broot-1.46.3/.cargo_vcs_info.json0000644000000001360000000000100122540ustar { "git": { "sha1": "c72c1200e45943e8e327a5afaac4561ac440cae2" }, "path_in_vcs": "" }broot-1.46.3/.github/FUNDING.yml000064400000000000000000000000211046102023000142120ustar 00000000000000github: [Canop] broot-1.46.3/.github/ISSUE_TEMPLATE/bug_report.md000064400000000000000000000001521046102023000172570ustar 00000000000000--- name: Bug report about: Create a report to help us improve title: '' labels: 'bug' assignees: '' --- broot-1.46.3/.github/ISSUE_TEMPLATE/feature_request.md000064400000000000000000000001661046102023000203170ustar 00000000000000--- name: Feature request about: Suggest an idea for this project title: '' labels: 'enhancement' assignees: '' --- broot-1.46.3/.github/workflows/tests.yml000064400000000000000000000004751046102023000163340ustar 00000000000000name: Tests on: push: branches: [ "main" ] pull_request: branches: [ "main" ] env: CARGO_TERM_COLOR: always jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Build run: cargo build --verbose - name: Run tests run: cargo test --verbose broot-1.46.3/.gitignore000064400000000000000000000003211046102023000130300ustar 00000000000000/bench.sh /compile.sh /termux-deploy.sh dev.log deploy.sh /*-deploy.sh /fix-win-toolchain.sh /releases /target /broot_*.zip /screens /website/site /build /trav /press /glassbench_*.db .bacon-locations .ignore broot-1.46.3/CHANGELOG.md000064400000000000000000001576621046102023000126760ustar 00000000000000### v1.46.3 - 2025-04-24 - fix broot waiting for events on internals like `:quit` - Fix #1006 ### v1.46.2 - 2025-04-21 - fix broken nushell script (`--max-depth` again) - Thanks @sandyspiers & @amitkot ### v1.46.1 - 2025-04-20 - fix nushell script broken by new `--max-depth` argument - Thanks @lizclipse ### v1.46.0 - 2025-04-16 - `:set_max_depth ` and `:unset_max_depth` - Fix #843 - Thanks @mcky - clear cache when files are deleted in staging area - Fix #999 - recompute preview transform when source file changed since last preview ### v1.45.1 - 2025-03-25 - Fix compilation failing without `--locked` - Fix #995 ### v1.45.0 - 2025-03-17 - Fix total search impossible to redo after refresh - Fix #986 - With `refresh_after: false`, a verb configuration can request that the tree isn't refreshed after its execution - Fix #987 ### v1.44.7 - 2025-02-12 - fix bad regex match position - Fix #979 - update resvg dependency to 0.44 - Thanks @NoisyCoil - on `--server`, remove the existing socket if it already exists - Thanks @VasilisManol ### v1.44.6 - 2025-01-12 -fix .ignore files ignored when not in a git repository - Fix #970 -update git2 dependency to 0.20 - Fix #974 ### v1.44.5 - 2025-01-02 - no real change (just reverting a crate name to ease some packaging) ### v1.44.4 - 2025-01-01 - fix panic in preview on syntax coloring (when a sublime syntax isn't compatible with the regex engine) - Fix #967 ### v1.44.3 - 2024-12-26 - removed default bindings on left and right keys. You may add them back by adding this to your verbs.hjson: ```Hjson { key: "left", internal: "back" } { key: "right", internal: "open_stay" } ``` - rustc minimal version changed from 1.76 to 1.79, which allows better performing image rendering - remove dependency to onig, to allow compatibility with gcc 15 - Fix #956 ### v1.44.2 - 2024-10-22 - temp files created for kitty now erased on quitting or when too many of them have been written - no longer panics when launched with BROOT_LOG=debug but the broot.log file can't be created - Fix #951 - fix user and group names displayed as "????" when coming from openldap - Fix #953 ### v1.44.1 - 2024-10-16 - fix wrong position of IMEs (input method editors) popup - thanks @xubaiwang - See #948 - improve querying the terminal for capabilities (prevent some escape chars from leaking) ### v1.44.0 - 2024-09-07 - `:focus_staging_area_no_open` internal, focus the staging area if it's already open, does nothing in other case - Fix #926 - fix some composite patterns with several operators and no parenthesis ### v1.43.0 - 2024-08-30 - 'Size' and 'Deletion date' columns in trash screen. This screen now supports the `:toggle_date`, `:toggle_size`, `:sort_by_date`, and `:sort_by_size` internals. - new `:show` internal make the provided path visible and selected, adding lines to the tree if necessary, does nothing if the provided path is not a descendant of the current tree root (this part may change depending on feedback) - Fix #936 ### v1.42.0 - 2024-08-18 - support of `.ignore` files with the same syntax than `.gitignore`. They have priority over `.gitignore` so that a personal `.ignore` file can override a shared `.gitignore` - See https://dystroy.org/broot/tree_view/#hidden-ignored-files - Fix #613 - `:toggle_ignore` internal, identical to `:toggle_git_ignore`, but with a clearer name so should be preferred - the `panels` verb filter now works in most contexts (it was previously only checked on key events) - many dependencies updated ### v1.41.1 - 2024-08-04 - allow compilation with rustc 1.76 - Fix #925 ### v1.41.0 - 2024-08-04 #### Major Feature: :search_again ctrl-s now triggers `:search_again` which either - brings back the last used search pattern, when no filtering pattern is active - does a "total search" if a filtering pattern is active and the search wasn't complete #### Major Feature: internals changing panel widths * `set_panel_width`, taking as parameter the index of the panel and the desired width * `move_panel_divider`, taking as parameter the index of the divider and the desired change `ctrl-<` is bound by default to `:move_panel_divider 0 -1` `ctrl->` is bound by default to `:move_panel_divider 0 1` See http://dystroy.org/broot/panels/#resize-panels #### Minor Changes: - when git file infos are shown, and git ignored files aren't hidden, those files are flagged with a 'I' - Fix #916 - Remove .bak extension from content search exclusion list - Fix #915 - Update nerdfont and vscode icons - Thanks @jpaju - `{initial-root}` verb argument ### v1.40.0 - 2024-07-16 #### Major Feature: preview transformers You can now define preview transformers to be applied before preview. They allow for example previewing PDF or Office files, or beautifying JSON files. Edit the `preview_transformers` array in your conf.hjson file. See https://dystroy.org/broot/conf_file/#preview #### Fixes - fix search on root - Fix #904 - fix some verb cycling problems - Fix #902 ### v1.39.2 - 2024-07-08 - fix UNC paths being displayed on Windows (regression at 1.39.1) - Fix #812 (again) ### v1.39.1 - 2024-07-05 - fix high-resolution (kitty protocole) image broken in release mode - Fix #885 - canonicalize paths when focusing them (mostly useful when following links) - a few minor internal optimizations ### v1.39.0 - 2024-05-31 - `:open_trash` shows the content of the trash. Other new internals & verbs: `:delete_trashed_file`, `:restore_trashed_file`, `:purge_trash` - Fix #855 - it's now possible to remove a default keybinding by defining a verb with no execution - Fix #632 - fix build on Android - thanks @dead10ck ### v1.38.0 - 2024-05-04 - `-{flags}` verb lets you change the state the same way you do it at start, eg `:-sd` to show sizes and dates - calling `:focus` on the tree root now goes up the tree (experimental) ### v1.37.0 - 2024-04-28 - optionally display lines surrounding a matching line in preview, with `lines_before_match_in_preview` and `lines_after_match_in_preview` - Fix #756 - filtered preview: jump between matches with `:next_match` (default: `tab`) and `:previous_match` (default `shift-tab`) - display setuid, setgid and sticky bits in permission - Fix #863, Thanks @Jisu-Woniu ### v1.36.1 - 2024-03-11 - fix ANSI code leaking to the input on start on Mac - Fix #854 ### v1.36.0 - 2024-03-01 - releases at github should be more `cargo binstall` friendly - Thanks @FrancescElies - improved `--help` - new `:stage_all_directories` internal - Fix #844 - `:print_tree` is one line shorter, so as to let the original shell command visible without scroll - fix and document the "kitty-csi-check" optional feature which can be enabled at compilation ### v1.35.0 - 2024-03-01 - Nerdfont icon theme - Fix #333 - Thanks @JonasLeonhard, @cho-m, @texastoland, @asdf8dfafjk and others ### v1.34.0 - 2024-02-24 - new `--verb-output` launch argument, dedicated to the new `:clear_output` and `:write_output` internals - Fix #825 - verb sequences (based on `cmd`) can take arguments from the verb invocation - don't fail launch in case of bad verb configuration, more helpful error message in such case - faster kitty image rendering by default - Fix #789 - `{file-git-relative}` verb argument - Thanks @VasilisManol - modify nushell function import: `use` instead of `source` - Thanks @texastoland and @FrancescElies - fix some resizing and flickering problems on Windows (appeared with 1.33.0) - Fix #840 - write `installed` flag file on `--install` - Fix #837 ### v1.33.1 - 2024-02-03 - fix the release's version ### v1.33.0 - 2024-02-03 - on terminals supporting the kitty keyboard protocol, you can now define and use key combinations like `space-n`, `ctrl-alt-a-b`, `shift-space-c`, `ctrl-enter`, etc. - new syntax for special paths - Fix #687, #669 ### v1.32.0 - 2024-01-02 - with "modal" enabled, `initial_mode` setting lets you choose whether to start in `input` mode or `command` mode (default) - Fix #708 ### v1.31.0 - 2023-12-30 - keep broot's work dir synchronized with the root of the current panel. Can be disabled in conf with `update_work_dir: false` - Fix #813 - fix `:trash` internal not working on staged files ### v1.30.2 - 2023-12-23 - don't canonicalize paths on windows on new panels - Fix #809 ### v1.30.1 - 2023-12-03 - nushell script: replace the deprecated `def-env` with `def --env` - Thanks @melMass ### v1.30.0 - 2023-12-03 - `:trash` internal - I'd like feedback on this one - Fix #799 - solve symlinks on `:panel_right` to display the dest path and the dest filesystem - Fix #804 - `:panel_right` on a directory now removes the filter - more '~' expansion in verb arguments ### v1.29.0 - 2023-11-22 - `terminal_title` option in configuration - Fix #794 - `:toggle_tree` internal and `--tree` and `--no-tree` launch flags (experimental, feedback welcome) - Fix #670 - Thanks @eldad - `{git-name}` verb argument ### v1.28.1 - 2023-11-13 - fix a regression in handling of rooted gitignore patterns - Fix #795 ### v1.28.0 - 2023-11-12 - left and right keys bound to verbs can be used when the input isn't empty, if they would have no effect to the input - default_flags now accept long parameters, including --cmd - Fix #790 - gitignore: fix relative patterns with several tokens - Fix #782 ### v1.27.0 - 2023-10-29 - the `apply_to` verb filter accepts new values: `text_file` and `binary_file`. Broot users editing files in their terminal (vi, emacs, etc.) should configure broot to open their text editor on `enter`: see https://dystroy.org/broot/tricks/#change-standard-file-opening - small breaking change: `:stage_all_files` now stages also symlinks - Fix #606 - new `{git-root}` verb argument - Fix 760 - Thanks @9999years - fix a freeze on windows when launching a search with `-c` - Thanks @3tilley - fix automatic preview pattern not escaping spaces and colons - Fix #778 ### v1.26.1 - 2023-09-30 - improved status line ### v1.26.0 - 2023-09-27 - when given a path to a file at launch, broot now selects it in the tree and opens it in preview - Fix #729 - allow rebinding of the 'tab' and 'esc' keys with the `:next_match` and `:escape` internals - Fix #740 - fix fuzzy patterns not case insensitive on some characters - Fix #746 ### v1.25.2 - 2023-09-20 - optional BROOT_CONFIG_DIR env var - the site now shows all env variables: https://dystroy.org/broot/launch/#environment-variables - `--only-folders` now longer allows symlinks to non directories - Fix #742 ### v1.25.1 - 2023-09-03 - fix shift-char in input extending the selection - Fix #733 ### v1.25.0 - 2023-08-19 - allow unescaped '::' in pattern position, experimental (might be removed) - allow hexa color notation in skins (eg `#fb0` or `#FFD700`) ### v1.24.2 - 2023-07-18 - fix a case of br script installation failing on Windows/Powershell ### v1.24.1 - 2023-07-16 - slightly better `--help` ### v1.24.0 - 2023-07-16 - installer for the powershell br script on windows - Thanks @felixkroemer - new `--help`, more compact - allow extra spaces before the verb - updated man page, now distributed in releases as /man/broot.1 ### v1.23.0 - 2023-06-16 - prettier, faster SVG rendering - reorganize default conf files, with a "skins" subfolder ### v1.22.1 - 2023-05-23 - allow dir computations in /run/media - Fix #704 - Thanks @jinliu - fix included solarized-dark.hjson skin file ### v1.22.0 - 2023-05-18 - define disk space availability colors in skin - Fix #705 - left elision of path when path/name doesn't fit - Fix #700 ### v1.21.3 - 2023-05-02 - `switch_terminal` verb parameter - Thanks @stevenxxiu - on Windows, when using `-c`, clear events after delay - Fix #699 ### v1.21.2 - 2023-03-30 - update dependencies because of some yanked ones ### v1.21.1 - 2023-03-23 - resolve `~` in special paths - Fix #685 - better clipboard support on MacOS - Thanks @bryan824 ### v1.21.0 - 2023-03-17 - better nushell integration (no need to quote arguments anymore, fix path extension broken by new version of nushell) - Thanks @stevenxxiu - don't show modal-only keys in help page when modal mode isn't enabled ### v1.20.2 - 2023-02-19 - fix debug statement printed in some cases (mostly on Windows) - Fix #672 ### v1.20.1 - 2023-02-08 - fix status line not always displaying the hint of the input's verb - Fix #665 ### v1.20.0 - 2023-02-03 - unless overridden, `/proc` is now `no-enter`, which solves freezes when searching on `/` in some system - See #639 - SVG files now rendered as images in the preview panel - new version of the nushell function. You should be prompted for an update - Fix #656 - Thanks @FrancescElies and @mediumrarez - `no-hide` special paths - Thanks @Avlllo - preview can now be opened on directories, showing their first level - Fix #405 - better determine whether the terminal is white or dark in some (probably rare) cases - See https://github.com/Canop/terminal-light/issues/2 ### v1.19.0 - 2023-01-03 - Nushell support - Fix #375 - Thanks @FrancescElies, @mediumrarez, and issue contributors ### v1.18.0 - 2022-12-21 - Hjson configuration file can now omit outside braces (it's "braceless Hjson"), making it much cleaner - allow opening the help screen with just the `?` key on Windows (as for other systems) - fix a crash in some cases of input being cleaned with a selection - Fix #643 ### v1.17.1 - 2022-12-15 - Windows specific implementation of :cpp ### v1.17.0 - 2022-12-09 - max file size for content search now configurable (default is now 10MB) - Fix #626 - file summing now avoids /proc and /run - default configuration sets /media as not entered by default (can be commented out, of course) ### v1.16.2 - 2022-11-04 - you can restrict the panels in which verbs apply with the verb configuration `panels` parameter - fix rm on Windows behaving "recursively" (it was `cmd /c del /Q /S {file}`) - Fix #627 ### v1.16.1 - 2022-10-13 - fix ctrl-left not usable anymore in filtered preview to remove filtering ### v1.16.0 - 2022-10-07 - status messages now displayed on toggling (for example showing hidden files) - upgrade terminal-light to 1.0.1 for better recognition of background color on high precision color terminals - in default configuration, ctrl-left never opens a panel to the left, as I think this was most often unwanted (one too many hit on cltr-left). It's possible to get the old behavior by binding ctrl-left to `:panel_left` instead of the new `:panel_left_no_open` internal. - New escaping rules let you skip many `\`, especially when building regexes - See new rules at https://dystroy.org/broot/input/#escaping - Fix #592 ### v1.15.0 - 2022-09-24 - with `show_matching_characters_on_path_searches: false`, it's possible to show only file names even when searching paths - Fix #490 - `--sort-by-type-dirs-first` and `--sort-by-type-dirs-last` - Fix #602 - modal: in input mode, uppercase letters don't trigger verbs anymore - Fix #604 - fix `:line_down_no_cycle` which was cycling - Fix #603 - selecting lines up or down with the mouse wheel now wraps in both direction (ie going up when your on top brings you to the bottom, and vice-versa) - `:select` internal, which can be used to select a visible file when given a path as argument. Experimental ### v1.14.3 - 2022-09-12 - fix crash with token searches - Fix #504 - Thanks @FedericoStra ### v1.14.2 - 2022-07-11 - Terminal background luma determination now works on all tested unixes, including MacOS - Fix #575 - Allow `:focus` based verbs to take a pattern - Fix #389 ### v1.14.1 - 2022-07-06 Due to a technical problem, background color based skin selection is disabled on non linux systems. ### v1.14.0 - 2022-07-05 #### Major Feature: imports A configuration file can now import one or several other ones. An import can have a condition on the terminal's background color, which makes it possible to import either a dark or a light theme depending on the current terminal settings. You're also encouraged to split your configuration in several files, as is now done for the default configuration. ### Minor changes - fix `--cmd` not working (it was accidentally renamed in `--commands`, `-c` was still working) - Fix #570 ### v1.13.3 - 2022-06-19 - fix `default_flags` in conf not working anymore - Fix #566 ### v1.13.2 - 2022-06-18 - advice to hit alt-i and|or alt-h when no file is visible - Fix #556 - examples on search modes in help screen - Fix #559 - list of syntactic themes in default conf - the --file-export-path launch argument which was deprecated since broot 1.6 has been removed (redirect the output of broot instead) - better built-in verbs for Windows - Thanks @Spacelord-XaN - take the .git/info/exclude file into account for ignoring - Thanks @refi64 Note: The released archive doesn't include an Android build - see https://github.com/Canop/broot/issues/565 ### v1.13.1 - 2022-05-30 - fix alt-enter failing to cd to directory ### v1.13.0 - 2022-05-29 - close the staging area when it's emptied with a verb (e.g. on `:rm`) - format files counts with thousands separator - Fix #549 - try verbs in order allowing some with filters before one without - Fix #552 ### v1.12.0 - 2022-05-05 - `:stage_all_files` internal, adding to the staging area all the files verifying the current pattern. Mapped by default to ctrl-a ### v1.11.1 - 2022-04-04 - fix broot not being usable while an image is being opened by hitting enter on linux - Fix #530 ### v1.11.0 - 2022-04-02 - sorting by type, with 3 new internals: `:sort_by_type_dirs_first`, `:sort_by_type_dirs_last`, and `:sort_by_type`. The last one lets you toggle between no sort, sorting by type with directories first, and sorting by type with directories last. - Fix #467 ### v1.10.0 - 2022-03-29 - verb filtering on file extension - Fix #508 - don't quit on tiny terminals - Fix #511 - fix the `capture_mouse` config item which was described in documentation but not usable (the non documented `disable_mouse_capture` argument was working and is kept for compatibility) ### v1.9.4 - 2022-03-07 - don't query size of remote filesystems anymore. This fixes some 10 seconds hangs in some cases (e.g. filesystem screen) when a remote filesystem is unreachable ### v1.9.3 - 2022-02-15 - keep same line visible in preview when resizing - `:previous_dir` and `:next_dir` internals - Fix #502 ### v1.9.2 - 2022-01-23 - instead of crashing on syntect panic in code preview, fall back to unstyled text - Fix #485 - fix files in worktree missing from git statuses - Fix #428 ### v1.9.1 - 2022-01-07 - fix a few problems of speed, flickering and uncleaned background with high resolution image preview ### v1.9.0 - 2022-01-06 - total search (launched with ctrl-s) shows all matches - This is experimental and might be reversed, opinions welcome - kitty graphics protocol used for high definition image rendering on recent enough versions of WezTerm - Fix #473 - fix syntaxic preview of Python files broken by comments - Fix #477 - home key bound to :input_go_to_start, end key bound to :input_go_to_end - Fix #475 ### v1.8.1 - 2021-12-29 - fix regex pattern automatically built from content pattern when going from a tree search to a file preview isn't escaped - Fix #472 ### v1.8.0 - 2021-12-26 - alt-i bound to toggle_git_ignore - alt-h bound to toggle_hidden - text previews switches to hexa when there are not printable chars (eg escape sequences) ### v1.7.5 - 2021-12-16 - Make the "clipboard" feature non default again, as it proves to make compilation harder on some platform. I still distribute executables with this feature and you can still try the compilation with `cargo install broot --features "clipboard"` ### v1.7.4 - 2021-12-01 - Fix 1 or 2 characters of the right ASCII column in hex view sometimes lost ### v1.7.3 - 2021-11-19 - Fix rendering artefacts on Windows, like a duplicate input line ### v1.7.2 - 2021-11-18 - include more syntaxes for preview of code files (using the list from the bat project) - Fix #464 ### v1.7.1 - 2021-11-07 - fix clipboard filled with dummy value on launch on X11 ### v1.7.0 - 2021-10-30 - "clipboard" feature now default (can still be removed at compilation with `--no-default-features`) - fix clipboard features not working on some recent linux distributions - you can now select part of the input with shift arrows or by dragging the mouse cursor - new internals: input_selection_cut and input_selection_copy (not bound by default) ### v1.6.6 - 2021-10-22 - make it possible to rebind left and right arrow keys without breaking usage in input - Fix #438 ### v1.6.5 - 2021-10-01 - improve decision on whether to trim root - Fix #434 - better make the tree's selected line visible ### v1.6.4 - 2021-10-01 - better scrolling behaviors - Fix #419 - fix special-path::Enter for symlinks - Fix #448 ### v1.6.3 - 2021-08-02 - hjson: fix bad parsing on tab before colon - now checks all args of externals are set, doesn't use the raw {arg} ### v1.6.2 - 2021-07-31 - broot reads now both the TERM and TERMINAL env variables to try determine whether the terminal is Kitty - using `:toggle_device_id`, you can display the device id of files (unix only) - fix a few problems with filesystems analysis by upgrading lfs-core to 0.4.2 - Fix #420 - a few minor rendering improvements ### v1.6.1 - 2021-06-23 - fix compilation on freeBSD - fix `:filesystems` view not listing disks whose mount point has a space character - fix panic on searching `cr/.*` if a file starts with an empty line - Fix #406 - fix preview of linux pseudo-files - identify "RAM" and "crypted" disks in `:filesystems` view ### v1.6.0 - 2021-06-16 - `{root}` argument (current tree root) can be used in verb patterns - Fix #395 - `working_dir` verb attribute - Fix #396 - client-server mode fixed, no longer feature-gated (but still only available on unix like systems) - broot tries to keep same selection on option changes - `:tree_up` and `:tree_down` internals, mapped to ctrl-up and ctrl-down - Fix #399 - better handling of auto color mode: two separate behaviors: for app running and for export when leaving - Fix #397 - remove the deprecated `--no-style` launch argument (use `--color no` instead) - deprecate the `--out` argument (redirecting the output is the recommended solution) - fix a few minor bugs ### v1.5.1 - 2021-06-03 - fixed a few problems with the `:del_word_right` internal ### v1.5.0 - 2021-06-02 - new `auto_exec` verb property: a non-auto_exec verb isn't executed directly on a keyboard shortcut but fills the input so that it may be edited before execution on enter key - add support for backtab key (by default it's bound to :previous_match) - `:rename` built-in verb, best used with its keyboard shortcut F2 - new standard verb arguments: `{file-stem}`, `{file-extension}`, and `{file-dot-extension}`, - new `:toggle_second_tree` internal - Fix #388 - total size of staging area computed and displayed if sizes displayed elsewhere - new `file_sum_threads_count` conf property to define the number of threads used for file summing (size, count, last modified). The goal is to more easily search what's the best value depending on the cpu, OS and disk type/speed - `:input_clear` internal - Fix #24 ### v1.4.0 - 2021-05-11 - the default (non prefixed) search is now "path fuzzy" instead of "name fuzzy". You can still change the default mode and mode bindings in the config. This was done after a survey in chat. - new "unordered tokens" search type: `t/ab,cd` searches for tokens "ab" and "cd" in any order and case insensitive in the subpath, matches for example `src/dcd/Bab.rs` - Fix #378 - fix search modes configuration removing all default mappings - Fix #383 - conf / quit_on_last_cancel to allow quitting with esc when there's nothing to cancel - Fix #380 - new `parent` skin entry for the part of the sub-path before the file name (visible when you search on subpath) - when a content search has been done, opening a file with a compatible command (like the standard `:edit`) opens on the first line with a match ### v1.3.1 - 2021-04-30 - fix `:previous_match` not jumping over indirect matches - Fix #377 - fix typing a prefixed pattern then emptying it while keeping the prefix doesn't remove filtering - Fix #379 - fix shifted matching chars highlighting with regex patterns when showing icons - Fix #376 ### v1.3.0 - 2021-04-28 #### Minor changes: - modal mode: revert to command mode on command execution - Fix #372 - modal mode: when in command mode, '/' only enters input mode and is never appended to the input - better handle failing external programs when not leaving broot #### Major feature: staging area You may add files to the staging area then apply a command on all of them. This new feature is described [here](https://dystroy.org/broot/staging-area). Several verbs have been added. Type "stag" in help to see them and their keyboard shortcuts. ### v1.2.10 - 2021-04-03 - fix shift based key shortcuts - Fix #363 - check there's another panel before executing verbs with other-panel argument - Fix #366 ### v1.2.9 - 2021-03-18 - fix panic on `:input_del_word_left` - Fix #361 - remove diacritics and normalize unicode from input on fuzzy search (an unnormalized string with unwanted diacritics most often happen when you paste a string in the input) ### v1.2.8 - 2021-03-11 - it's possible to define several key shortcuts for a verb, using the "keys" property - improvements of fuzzy matching ### v1.2.7 - 2021-02-28 - don't ask again for installation if no sourcing file has been found ### v1.2.6 - 2021-02-27 - clipboard features (copy and paste verbs) now work on Android/Termux (needs the Termux API to be installed) - fix a compilation problem on non gnu windows - Thanks @Stargateur - obey '--color no' even in standard application mode. In that case, automatically enable selection marks or you wouldn't know what line is selected ### v1.2.5 - 2021-02-25 - fix style characters being written in `--no-style` mode - Fix #346 - replace `--no-style` with `--color` taking `yes`, `no` or `auto`, with detection of output being piped in `auto` mode (default). `--no-style` is still usable but it's not documented anymore - Fix #347 - fix wrong version number written in log file - Fix #349 - by default the number of panels is now limited to 2 (can be changed in conf with `max_panels_count`). The goal is to improve the global ergonomics for the most common (universal?) use case - Fix #345 ### v1.2.4 - 2021-02-14 - :line_down_no_cycle and :line_up_nocycle. They may be mapped instead of :line_up and :line_down when you don't want to cycle (ie arrive on top when you go down past the end of the tree/list) - Fix #344 - fix selected line number rendering in text preview ### v1.2.3 - 2021-02-06 - special paths in "no-enter" or "hide" aren't counted when summing sizes or dates. It's a compromise: it makes all sums a little slower, especially if you have a lot of special paths or complex ones, but it allows skipping over the very slow disks and thus makes some cases much faster - Fix #331 - br fish shell function uses shell completion of broot - tree height in `:pt` now applies even when there are more root items (thus truncating the tree) - Fix #341 - fix the F5 and F6 shortcuts (copy and move between panels) in the default configuration ### v1.2.1 - 2021-01-27 - allow dashes instead of underscores in conf property names. This fixes a regression as "special-paths", "ext-colors" and "search-modes" were defined with a dash up to version 1.0.7. Now both spellings are OK - Fix #330 - fix some problems with paths containing spaces (regression since 1.1.11)- Fix #329 ### v1.2.0 - 2021-01-14 - experimental "modal mode" (or "vim mode") in broot. See https://dystroy.org/broot/vim_mode/ - fix mouse staying captured during external app execution - Fix #325 ### v1.1.11 - 2021-01-07 - fix handling of rules starting with '/' in the global gitignore - Fix #321 - alt-c now mapped to the new :copy_line verb which, when in tree, puts the selected path in the clipboard and, when in text preview, puts the selected text line in the clipboard - Fix #322 - it's possible to define verb execution patterns as arrays instead of simple strings, to avoid having to escape quotes - Fix #319 ### v1.1.10 - 2020-12-24 broot now accepts both TOML and Hjson files for configuration. Default is Hjson. I explain the change [here](https://dystroy.org/blog/hjson-in-broot/) ### v1.0.9 - 2020-12-19 - fix handling on quotes in configured verbs - Fix #316 ### v1.0.8 - 2020-12-01 - when sizes are displayed (eg on `br -s`), show size of root line and root filesystem info - modified size cache management makes some size computations faster - sizes (and dates and counts) are progressively displayed ### v1.0.7 - 2020-11-27 * :previous_same_depth and :next_same_depth internals * in kitty terminal, image preview is high definition ### v1.0.6 - 2020-11-19 * optional icons, thanks to @asdf8dfafjk (@fiAtcBr on Miaou) - See https://dystroy.org/broot/icons * dev.log renamed into broot.log * `:line_up` and `:line_down` accept an optional count as argument - Fix #301 ### v1.0.5 - 2020-11-05 * in case of IO error when previewing a file, display the error instead of quitting * fix regression related to display of texts with characters taking several columns * preview now supports opening system files with size 0 (eg /proc "files") ### v1.0.4 - 2020-10-22 * don't use absolute paths for built-in verbs * fix freeze on circular symlink chains * `:filesystems` (alias `:fs`) display all mounted filesystems in a filtrable view. You can enter to browse at the mount point (unix only for now) * `:toggle_root_fs` (alias `:rfs`) toogles showing information on the filesystem of the current directory * filesystem information (mainly size and usage) related to the current filesystem displayed in whale-spotting mode ### v1.0.3 - 2020-10-07 * change the syntax of cols_order in conf * fix left key moving the cursor to start of input (instead of just one char left) ### v1.0.2 - 2020-10-04 * `cr/` patterns search on file content with regular expressions * search modes and their prefixes listed in help ### v1.0.1 - 2020-09-30 * don't apply .gitignore files (including the global one) when not in a git repository - Fix #274 * the "clipboard" optional feature adds: * the `:copy_path` verb which copies the selected path to the clipboard (mapped to alt-c) * the `:input_paste` verb which inserts the clipboard content in the input (mapped to ctrl-v) * it's now possible to define verbs executing sequences of commands - Fix #277 * fix opening of link of link - Fix #280 * broot is now compatible with Android, you can use it on Termux for example * help page lists all optional features enabled at compilation * list of verbs in help page is searchable ### v1.0.0 - 2020-09-01 - nothing new, which is better when you want to call your software stable ### v0.20.3 - 2020-08-23 - fix a few problems with tabulation rendering - fix a few cases of files being called "huge" while they're only very big ### v0.20.2 - 2020-08-18 - fix esc key not removing the filter in text preview ### v0.20.1 - 2020-08-18 - completion of the "client-server" feature - the tree tries to keep the selection when you remove a filter using the esc key - :focus now has a shortcut for when a file is selected too: ctrl-f - show_selection_mark preference in config (mostly for cases the background isn't clear enough) - **breaking change:** The working directory of external processes launched by broot isn't set anymore by default. If you want it to be changed, add `set_working_dir = true` to the verb definition. ### v0.20.0 - 2020-08-16 - it's now possible to launch a terminal as sub process from broot (and be back to broot on exit) - the selected directory is now the working dir for subprocess launched from broot - images are previewed as such - :preview_binary, :preview_text, and :preview_image verbs allow the choice of previewing mode - fix a possible panic in previewed files on displaying fuzzy pattern matches ### v0.19.4 - 2020-07-31 - don't install the br shell function when --outcmd is set or $BR_INSTALL is "no" - Fix #265 - more relevant status hints - Fix #261 ### v0.19.3 - 2020-07-27 - refined search in preview interaction (see blog https://dystroy.org/blog/broot-c-search/) ### v0.19.2 - 2020-07-26 - "client-server" feature (see client-server.md) - preview's pattern is kept when changing file - selected line in preview, interesting when removing the pattern (to see what's around a match) - faster availability of huge files in preview - search in preview now interrupted by key events (just like the trees) - a content search in a tree is propagated as a regex in a preview on :panel_right (ctrl-right) - syntax theme choice in conf.toml - {line} in a verb execution pattern refers to the line number ### v0.19.1 - 2020-07-17 Force trimming root when searching (trimming root when not searching is no longer the default) ### v0.19.0 - 2020-07-16 #### Major feature: the preview panel Hit ctrl-right when a file is selected and you get the preview. ### v0.18.6 - 2020-07-10 - `[ext-colors]` section in config - a few minor fixes and changes ### v0.18.5 - 2020-07-05 - git status takes into account overloading of enter and alt-enter - a few minor fixes and changes ### v0.18.4 - 2020-07-02 - `--git-status` launch option - fix rendering on windows ### v0.18.3 - 2020-06-30 Faster rendering (0.18.2 made it slower on some terminals) ### v0.18.2 - 2020-06-29 Remove flickering ### v0.18.1 - 2020-06-28 Column order is now configurable - Fix #127 ### v0.18.0 - 2020-06-26 #### Major change: Recursive last modified date computation The date of directories is now the modification date of the last modified inner file, whatever its depth. This is computed in the background and doesn't slow your navigation. #### Major change: Sort mode Size can now be displayed out of sort mode, which concerns either size or dates. There are new launch arguments: * `--sort-by-count` : sort by number of files in directories * `--sort-by-date` : sort by dates, taking content into account (make it easy to find deep recent files) * `--sort-by-size` : sort by size * `--whale-spotting` or `-w` : "whale spotting" mode (sort by size and show all files) The `-s` launch argument now works similarly to -d or -p : it doesn't activate a sort mode but activates showing the sizes. `-s` has been replaced with `-w`. Similarly new verbs have been defined: * `:toggle_counts`, with shortcut `counts` shows the number of files in directories * `:toggle_sizes`, with shortcut `sizes` shows the sizes of files and directories * `:sort_by_count` has for shortcut `sc` * `:sort_by_date` has for shortcut `sd` * `:sort_by_size` has `ss` as shortcut * `:no_sort` removes the current sort mode, if any ### v0.17.0 - 2020-06-21 #### Major feature: keep broot open behind terminal editors If you now open vi or emacs from broot with `leave_broot = false` you should be back in broot after you quit the editor - Fix #34 - Fix #144 - Fix #158 #### Minor changes: - it's possible to define input edition shortcuts - Fix #235 - MacOS: config directory for new install is ~/.config/broot - Fix #103 ### v0.16.0 - 2020-06-20 #### Major feature: composite patterns It's now possible to use logical operators on patterns. For example: * `!/txt$/` : files whose name doesn't end in "txt" * `carg|c/carg` : files whose name or content has "carg" * `(json|xml)&c/test` : files containing "test" and whose name fuzzily contains either "json" or "xml" The document contains other examples and precisions. ### v0.15.1 - 2020-06-12 - fix some problems related to relative paths in built in cp and mv ### v0.15.0 - 2020-06-12 #### Major feature: new input syntax - Breaking Change New search modes (see https://dystroy.org/broot/input/#the-filtering-pattern) : - fuzzy or regex on sub-paths (the path starting from the displayed root) - search in file content - it's possible to configure how search modes are selected in config - search pattern characters can be escaped with a '\' #### Minor changes: - tab goes to next direct match when there's no verb in input - Fix #234 - `:open_stay_filter` to be used if you want to keep the pattern when you navigate - Fix #240 - mouse capture can be disabled with `capture_mouse = false` - Fix #238 - several small fixes ### v0.14.2 - 2020-06-01 - `apply_to` verb property - fix #237 ### v0.14.1 - 2020-05-29 - fix uppercase letters ignored in input field ### v0.14.0 - 2020-05-29 #### Major feature: `:focus` verb This verb can be called, and parameterized, with a path as argument, which makes it possible to have a shortcut to a specific location. As a result, the specific `:focus_user_home` and `:focus_root` verbs have been removed (`:focus ~` works on all OS). #### Major feature: panels! There are three major ways to open a new panel: - by using ctrl-left or ctrl-right, which can also be used to navigate between panels - when a verb is edited, by using ctrl-p, which opens a panel which on closure will fill the argument - by using any verb with a bang. For example `:focus! ~` or `:!help` When you have two panels, you may use some new verbs like :copy_to_panel which copies the selection to the selected location in the other panel. Many new verbs and functions are related to panels but broot can still be used exactly as before without using panels. #### Major feature: autocompletion Using the Tab key you can complete verbs or paths #### Major feature: special paths Some paths can be handled in a specific way. Fix #205 and #208 You can for example decide that some slow disk shouldn't be entered automatically #### Minor changes: - date/time format configurable - Fix #229 - esc doesn't quit broot anymore (by popular demand) It's probably a good idea to remove your existing conf.toml file so that broot creates a brand new one with suggestions of shortcuts. ### v0.13.6 - 2020-04-08 - ignore diacritics in searches - Fix #216 ### v0.13.5 - 2020-03-28 - right key open directory, left key gets back (when input is empty) - Fix #179 - replace ~ in path arguments with user home dir - Fix #211 - use $XDG_CONFIG_HOME/git/ignore when the normal core.excludesFile git setting is missing - Fix #212 - add a man page to archive - Fix #165 ### v0.13.4 - 2020-03-13 - support for an arg made of an optional group - Fix #210 ### v0.13.3 - 2020-02-27 - fix a compilation problem related to dependency (termimad) version ### v0.13.2 - 2020-02-16 - fix -i and -I launch arguments being ignored (fix #202) ### v0.13.1 - 2020-02-08 - fix background not always removed when skin requires no background (Fix #194) ### v0.13.0 - 2020-02-05 #### Major change: git related features - `:show_git_file_info` compute git repo statistics and file statuses. Statistics are computed in background and cached. - `:git_diff` verb launching `git diff {file}` - `:git_status` filter files to show only the ones which are relevant for `git status` (warning: slow on big repositories) #### Major change: rewamped launch flags Several new launch flags have been added, mostly doing the opposite of previous ones (eg `-S` negates `-s`) and a new entry in the conf.toml lets you define default flags (which can be overridden by the ones you pass on the command line). Do `br --help` to view the complete list of flags. #### Minor changes: - on refresh or after command, if the previously selected path can't be selected (missing file, probably) then the previous index will be kept if possible - alt-enter can be rebinded (users should not do that without binding `:cd`, though) ### v0.12.2 - 2020-01-29 - fix Ctrl-J being interpreted as Enter (fix #177) ### v0.12.1 - 2020-01-25 - fix panic on some inputs starting with a `/` (Fix #175) - TAB key now jumps to direct matches only - `--conf` arg to launch broot with specific config file(s) (fix #141) ### v0.12.0 - 2020-01-19 - **breaking change:** commands given with `--cmd` must be separated (default separator is `;`) - fix some cases of terminal let in a bad state on errors (thanks Nathan West) - bring some changes to the fish shell function and its installation (PR #128) - consider path `$ZDOTDIR/.zshrc` for zsh shell function sourcing (fix #90) - don't use .gitignore files of parent repositories - change default value of the toggle_trim_root to false (fix #106 but might be reverted) - `:print_relative_path` verb (fix #169, thanks Roshan George) - `:chmod` verb ### v0.11.9 - 2020-01-15 - fix a case of bad selection after search followed by interrupted search (#147) - `--set-install-state` can be used in tests or manual installs to set the installation state - Raspberry now a default target available in installation page - fix a regression: `br -s` not finishing computing size until receiving an event - display the real size of sparse files (fix #102) ### v0.11.8 - 2020-01-12 - set different skins for the r, w and x parts of the mode (permission) - compatibility with freeBSD - generate shell completion scripts on build (deep into the target directory) - `--print-shell-function` launch argument to print the shell functions to stdout ### v0.11.7 - 2020-01-11 - fix cancelled verbs possibly executed (fix #104) (major dangerous bug) ### v0.11.6 - 2020-01-10 - backspace was previously bound to :back if not consumed by input. This is removed - fix unsignificative event interpreted as previous event repetition - fix wrong background applied on sizes in tree display - allow env vars used in verb execution to contain parameters (fix #114) - allow the use of arrow keys as triggers for verbs (fix #121) - fix scroll adjustment when using the arrow keys (when there's a scrollbar) (fix #112) ### v0.11.5 - 2020-01-10 - keep same path selected when lines are reordered (such as when directory sizes are computed - changed the skin used before installation so that it works better on white backgrounds ### v0.11.4 - 2020-01-09 - make :open_stay and :open_leave work in help screen (applying on configuration file) - Mac/fish: use ~/.config/fish even on systems where the config home is usually different - Mac/bash: add .bash_profile to the list of possible sourcing files - define ctrl-c as a new way to quit ### v0.11.3 - 2020-01-09 - fix the 'n' answer being ignored when user is asked authorization ### v0.11.2 - 2019-12-30 - fix alt-enter not recognized on some computers ### v0.11.0 - 2019-12-21 New major feature: the `:total_search` verb, normally triggered with *ctrl-s*: done after a search it repeats it but looks at **all** the children, even if it's long and there were a lot of matches ### v0.10.5 - 2019-12-20 - should not panic anymore when opening arbitrary files on server - allow more keys for verbs. For example you can use `enter` (this one won't apply on directories but only on files) - display all possible verb completions in status - don't query the terminal size after start: use the new Resize event of Crossterm ### v0.10.4 - 2019-12-16 * fuzzy search performance improvement * verb invocation now optional so that a verb can be defined to just introduce a keyboard shortcut * owner and group separately skinned * screen redrawn on resize (but tree not recomputed, you may want to do F5 to have the best sized tree) * changes in br shell function storage and sourcing from fish, bash, and zsh. Fixes #39 and #53. Note that broot will ask you again to install the br function ### v0.10.3 - 2019-11-27 * fix panic on doing `:rm` on the last child of current root * refactor help page generation using Termimad templates * clear help background when terminal was resized between redraws ### v0.10.2 - 2019-11-15 * colored status line * better handling of errors when opening files externally * spinner replaced with an explicit text * `:parent` no longer keeps the filter (this was too confusing) * new `:up` command, focusing the parent of the current tree root * `$PAGER` used in default config. Fix #20 * default conf links to the white background skin published on web site * new "default" entry in skin, to define a global background replacing the terminal's one ### v0.10.1 - 2019-11-04 * incorporate crossterm 0.13.2 to fix a regression in vi launch (see https://github.com/Canop/broot/issues/73) ### v0.10.0 - 2019-11-03 * moved to the crossterm 0.13 and termimad 0.7.1 * broot runs on stderr, * broot can run in a subshell Those changes allow tricks like `my_unix_command "$(broot)"` when you do `:pp` to print the path on stdout from broot ### v0.9.6 - 2019-09-20 * smarter cut of the status line when it doesn't fit the console's width * fix mouse click on the status line crashing broot * prevent the best match from being hidden inside "unlisted" matches ### v0.9.5 - 2019-09-15 * keyboard keys & shortcuts can be defined for more actions, all built-in verbs documented in website * paths built from verb arguments are now normalized ### v0.9.4 - 2019-09-13 New internal verbs like :focus_root, :focus_user_home, :refresh, :select_first You can define triggering keys for verbs. For example you can add those mappings: [[verbs]] invocation = "root" key = "F9" execution = ":focus_root" [[verbs]] invocation = "home" key = "ctrl-H" execution = ":focus_user_home" [[verbs]] invocation = "top" key = "F6" execution = ":select_first" [[verbs]] invocation = "bottom" key = "F7" execution = ":select_last" Then, when doing Ctrl-H, you would go to you user home (`~` when on linux) and F7 would select the last line of the tree. A few more keys are defined as default, like F1 for `:help` and F5 for `:refresh`. ### v0.9.3 - 2019-08-02 Launching broot with `--sizes` now sets a set of features enabling fast "whale spotting" navigation ### v0.9.2 - 2019-07-31 Fix non consistent builds due to lack of precise versioning in crossterm subcrate versioning ### v0.9.1 - 2019-07-29 #### Major change * A new syntax allows specifying verbs which can work on relative paths or absolute paths alike. For example the old definition of `cp` was invocation = "cp {newpath}" execution = "/bin/cp -r {file} {parent}{newpath}" and it's now invocation = "cp {newpath}" execution = "/bin/cp -r {file} {newpath:path-from-parent}" The :path-from-parent formatting means the token will be interpreted as a path, and if it's not starting with a / will be prefixed by the parent path. It's possible to also use `{subpath:path-from-directory}` where directory is parent only if the selected file isn't a directory itself. #### Minor changes - shift-tab selects the previous match - mouse wheel support (selection in tree, scroll in help) - the input field handles left/right arrow keys, home/end, click, and delete ### v0.9.0 - 2019-07-19 #### Major change The logic behind opening has changed to allow easier opening of files in non terminal applications without closing broot. **Old behavior:** - in case of enter or double-click - on a directory: open that directory, staying in broot - on a file: open the file, quitting broot - in case of alt-enter - on a directory: cd to that directory, quitting broot - on a file: cd to that file's parent directory, quitting broot **New behavior:** - in case of enter or double-click - on a directory: open that directory, staying in broot - on a file: open that file in default editor, not closing broot - in case of alt-enter - on a directory: cd to that directory, quitting broot - on a file: open that file in default editor, quitting broot #### Minor change - Hitting `?` more directly opens the help screen, even when executing a verb ### v0.8.6 - 2019-07-03 - Hitting enter when first line is selected, or clicking it, goes up to the parent directory - detect and color executable files on windows - new toggle to display dates of files (last modification) - a few small improvements ### v0.8.5 - 2019-06-20 - minor cosmetic changes (this version was mostly released to ensure consistency with termimad's crate) ### v0.8.4 - 2019-06-17 - apply verbs on link files, not on their targets (rm some_link was dangerous) ### v0.8.3 - 2019-06-16 - mouse support: click to select, double-click to open ### v0.8.2 - 2019-06-15 - fix wrong result of scrolling when help text fits the screen ### v0.8.1 - 2019-06-10 - change default skin to only use highly compatible colors - allow ANSI colors in skin configuration ### v0.8.0 - 2019-06-07 Half broot has been rewritten to allow Windows compatibility. Termion has been replaced with crossterm. ### v0.7.5 - 2019-04-03 - try to give arguments to verbs executed with --cmd - Hitting no longer quits when root is selected (many users found it confusing) ### v0.7.4 - 2019-03-25 - fix verbs crashing broot in / - fix user displayed in place of user with :perm ### v0.7.3 - 2019-03-22 - :print_tree outputs the tree. See [documentation](https://dystroy.org/broot/documentation/usage/#export-a-tree) for examples of use - F5 refreshes the tree ### v0.7.2 - 2019-03-15 - env variables usable in verb execution patterns, which makes it possible to use `$EDITOR` in default conf.toml - ctrl-u and ctrl-d are now alternatives to page-up and page-down - better error messages regarding faulty configurations - more precise errors in case of invalid regexes - use the OS specific file opener instead of xdg-open (concretly it means `open` is now used on MacOS) Thanks Ophir LOJKINE for his contributions in this release ### v0.7.1 - 2019-03-08 - fix a few problems with the count of "unlisted" files ### v0.7.0 - 2019-03-07 ##### Major changes - verbs can now accept complex arguments. This allows functions like mkdir, mv, cp, etc. and your own rich commands - custom verbs can be executed without leaving broot (if defined with `leave_broot=false`) ##### Minor changes - Ctrl-Q shortcut to leave broot - fix a case of incorrect count of "unlisted" files ### v0.6.3 - 2019-02-23 - `br` installer for the fish shell - faster directory size computation (using a pool of threads) - fix alt-enter failing to cd when the path had spaces - executable files rendered with a different color ### v0.6.2 - 2019-02-18 - all colors can be configured in conf.toml ### v0.6.1 - 2019-02-14 - complete verbs handling in help screen - faster regex search - fix missing version in `broot -V` ### v0.6.0 - 2019-02-12 ##### Major changes - broot now installs the **br** shell function itself *(for bash and zsh, help welcome for other shells)* - new verb `:toggle_trim_root` allows to keep all root children - verbs can refer to `{directory}` which is the parent dir when a simple file is selected - user configured verbs can be launched from parent shell too (like is done for `cd {directory}`) ##### Minor changes - allow page up and page down on help screen - fuzzy pattern: increase score of match starting after word separator - better handle errors on a few cases of non suitable root (like passing an invalid path) - clearer status error on `:cd`. Mentions `` in help - add a scrollbar on help screen ### v0.5.2 - 2019-02-04 - More responsive on slow disks - fix a link to documentation in autogenerated conf ### v0.5.1 - 2019-02-03 - alt-enter now executes `:cd` ### v0.5.0 - 2019-01-30 - patterns can be regexes (add a slash before or after the pattern) - configuration parsing more robust - no need to put all verbs in config: builtins are accessible even without being in config - no need to type the entire verb shortcut: if only one is possible it's proposed - verbs with {file} usable in help state: they apply to the configuration file - clear in app error message when calling :cd and not using the br shell function - bring back jemalloc (it's faster for broot) - more precise display of file/dir sizes ### 0.4.7 - 2019-01-21 - fix some cases of panic on broot quitting - new `--cmd` program argument allows passing a sequence of commands to be immediately executed (see [updated documentation](https://github.com/Canop/broot/blob/master/documentation.md#passing-commands-as-program-argument)) - better handling of symlink (display type of target, show invalid links, allow verbs on target) - compiled with rustc 1.32 which brings about 4% improvements in perfs compared to 1.31 ### v0.4.6 - 2019-01-12 - fix configured verbs not correctly handling paths with spaces - fix `:q` not instantly quitting broot when computing size - hit enter on tree root correctly quits broot ### v0.4.5 - 2019-01-11 - Faster search, mainly ### v0.4.3 - 2019-01-08 - Faster search and directory size computation. ### v0.4.2 - 2019-01-07 - more complete search if time allows - search pattern kept after verb execution ### v0.4.1 - 2019-01-07 - first public release broot-1.46.3/Cargo.lock0000644000002747450000000000100102520ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "adler2" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "ahash" version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", "once_cell", "version_check", "zerocopy 0.7.35", ] [[package]] name = "aho-corasick" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "aligned-vec" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1" [[package]] name = "allocator-api2" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "android-tzdata" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" [[package]] name = "android_system_properties" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" dependencies = [ "libc", ] [[package]] name = "ansi_colours" version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14eec43e0298190790f41679fe69ef7a829d2a2ddd78c8c00339e84710e435fe" dependencies = [ "rgb", ] [[package]] name = "anstream" version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" version = "3.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" dependencies = [ "anstyle", "once_cell", "windows-sys 0.59.0", ] [[package]] name = "anyhow" version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" [[package]] name = "arbitrary" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" [[package]] name = "arg_enum_proc_macro" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" dependencies = [ "proc-macro2", "quote", "syn 2.0.100", ] [[package]] name = "argh" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34ff18325c8a36b82f992e533ece1ec9f9a9db446bd1c14d4f936bac88fcd240" dependencies = [ "argh_derive", "argh_shared", "rust-fuzzy-search", ] [[package]] name = "argh_derive" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb7b2b83a50d329d5d8ccc620f5c7064028828538bdf5646acd60dc1f767803" dependencies = [ "argh_shared", "proc-macro2", "quote", "syn 2.0.100", ] [[package]] name = "argh_shared" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a464143cc82dedcdc3928737445362466b7674b5db4e2eb8e869846d6d84f4f6" dependencies = [ "serde", ] [[package]] name = "arrayref" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" [[package]] name = "arrayvec" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "autocfg" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "av1-grain" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6678909d8c5d46a42abcf571271e15fdbc0a225e3646cf23762cd415046c78bf" dependencies = [ "anyhow", "arrayvec", "log", "nom", "num-rational", "v_frame", ] [[package]] name = "avif-serialize" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98922d6a4cfbcb08820c69d8eeccc05bb1f29bfa06b4f5b1dbfe9a868bd7608e" dependencies = [ "arrayvec", ] [[package]] name = "base64" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bet" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a33cd5a98377df6ecfedff9c018c1c1ea92c730c4baf9173cfe0d7c3df20ab6" [[package]] name = "bincode" version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" dependencies = [ "serde", ] [[package]] name = "bit-set" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" dependencies = [ "bit-vec", ] [[package]] name = "bit-vec" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" [[package]] name = "bit_field" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" [[package]] name = "bitstream-io" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6099cdc01846bc367c4e7dd630dc5966dccf36b652fae7a74e17b640411a91b2" [[package]] name = "block2" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" dependencies = [ "objc2", ] [[package]] name = "broot" version = "1.46.3" dependencies = [ "ansi_colours", "base64 0.21.7", "bet", "char_reader", "chrono", "clap", "clap-help", "clap_complete", "clap_mangen", "cli-log", "crokey", "custom_error", "deser-hjson", "directories 4.0.1", "file-size", "flex-grow", "git2", "glassbench", "glob", "id-arena", "image", "include_dir", "is_executable", "lazy-regex", "lfs-core", "libc", "lru", "memmap2", "once_cell", "opener", "pathdiff", "phf", "rayon", "resvg", "rustc-hash", "secular", "serde", "smallvec", "splitty", "strict 0.1.4", "syntect-no-panic", "tempfile", "termimad", "terminal-clipboard", "terminal-light", "toml", "trash", "umask", "unicode-width", "uzers", "which", "xterm-query", ] [[package]] name = "bstr" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" dependencies = [ "memchr", "regex-automata", "serde", ] [[package]] name = "built" version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56ed6191a7e78c36abdb16ab65341eefd73d64d303fffccdbb00d51e4205967b" [[package]] name = "bumpalo" version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[package]] name = "bytemuck" version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6b1fc10dbac614ebc03540c9dbd60e83887fda27794998c6528f1782047d540" [[package]] name = "byteorder-lite" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "cc" version = "1.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362" dependencies = [ "jobserver", "libc", "shlex", ] [[package]] name = "cfg-expr" version = "0.15.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" dependencies = [ "smallvec", "target-lexicon", ] [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "cfg_aliases" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "char_reader" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37a59b22dec21ca7d6c173bd543eeab4cd2f36cf21f039a4134905034c87ed3a" [[package]] name = "chrono" version = "0.4.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "serde", "wasm-bindgen", "windows-link", ] [[package]] name = "clap" version = "4.5.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2df961d8c8a0d08aa9945718ccf584145eee3f3aa06cddbeac12933781102e04" dependencies = [ "clap_builder", "clap_derive", ] [[package]] name = "clap-help" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc01b70b5fd7e87b2ae778cfd151120355002f44ab1504e9943151a52cae8171" dependencies = [ "clap", "termimad", "terminal-light", ] [[package]] name = "clap_builder" version = "4.5.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "132dbda40fb6753878316a489d5a1242a8ef2f0d9e47ba01c951ea8aa7d013a5" dependencies = [ "anstream", "anstyle", "clap_lex", "strsim", ] [[package]] name = "clap_complete" version = "4.5.47" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06f5378ea264ad4f82bbc826628b5aad714a75abf6ece087e923010eb937fb6" dependencies = [ "clap", ] [[package]] name = "clap_derive" version = "4.5.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", "syn 2.0.100", ] [[package]] name = "clap_lex" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "clap_mangen" version = "0.2.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "724842fa9b144f9b89b3f3d371a89f3455eea660361d13a554f68f8ae5d6c13a" dependencies = [ "clap", "roff", ] [[package]] name = "cli-log" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e220aa46e5395cd473a054f8e7e52403108ce147a4eb68c001afb01672a4e046" dependencies = [ "chrono", "file-size", "log", "proc-status", ] [[package]] name = "clipboard-win" version = "4.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7191c27c2357d9b7ef96baac1773290d4ca63b24205b82a3fd8a0637afcf0362" dependencies = [ "error-code", "str-buf", "winapi", ] [[package]] name = "clipboard_macos" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b7f4aaa047ba3c3630b080bb9860894732ff23e2aee290a418909aa6d5df38f" dependencies = [ "objc2", "objc2-app-kit", "objc2-foundation", ] [[package]] name = "color_quant" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] name = "colorchoice" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "coolor" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "691defa50318376447a73ced869862baecfab35f6aabaa91a4cd726b315bfe1a" dependencies = [ "crossterm", ] [[package]] name = "core-foundation-sys" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "core_maths" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77745e017f5edba1a9c1d854f6f3a52dac8a12dd5af5d2f54aecf61e43d80d30" dependencies = [ "libm", ] [[package]] name = "crc32fast" version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ "cfg-if", ] [[package]] name = "crokey" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5ff945e42bb93d29b10ba509970066a269903a932f0ea07d99d8621f97e90d7" dependencies = [ "crokey-proc_macros", "crossterm", "once_cell", "serde", "strict 0.2.0", ] [[package]] name = "crokey-proc_macros" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "665f2180fd82d0ba2bf3deb45fafabb18f23451024ff71ee47f6bfdfb4bbe09e" dependencies = [ "crossterm", "proc-macro2", "quote", "strict 0.2.0", "syn 2.0.100", ] [[package]] name = "crossbeam" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8" dependencies = [ "crossbeam-channel", "crossbeam-deque", "crossbeam-epoch", "crossbeam-queue", "crossbeam-utils", ] [[package]] name = "crossbeam-channel" version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-deque" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-queue" version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crossterm" version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" dependencies = [ "bitflags 2.9.0", "crossterm_winapi", "mio", "parking_lot", "rustix 0.38.44", "signal-hook", "signal-hook-mio", "winapi", ] [[package]] name = "crossterm_winapi" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" dependencies = [ "winapi", ] [[package]] name = "crunchy" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" [[package]] name = "csv" version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf" dependencies = [ "csv-core", "itoa", "ryu", "serde", ] [[package]] name = "csv-core" version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d02f3b0da4c6504f86e9cd789d8dbafab48c2321be74e9987593de5a894d93d" dependencies = [ "memchr", ] [[package]] name = "csv2svg" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cc2a6decf9570c8fb8e9620c1f2f73ed56f63d125bef5bc7d05dd6cedf2c171" dependencies = [ "anyhow", "argh", "chrono", "cli-log", "crossterm", "csv", "directories 5.0.1", "libc", "log", "num-traits", "open", "svg", "tempfile", ] [[package]] name = "custom_error" version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f8a51dd197fa6ba5b4dc98a990a43cc13693c23eb0089ebb0fcc1f04152bca6" [[package]] name = "data-url" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a" [[package]] name = "deranged" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" dependencies = [ "powerfmt", ] [[package]] name = "deser-hjson" version = "2.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d94aac4095c08ded7e4b9ba7fc2b2929f11b94bb96897ca188b0f64e01688e1" dependencies = [ "serde", ] [[package]] name = "directories" version = "4.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f51c5d4ddabd36886dd3e1438cb358cdcb0d7c499cb99cb4ac2e38e18b5cb210" dependencies = [ "dirs-sys 0.3.7", ] [[package]] name = "directories" version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35" dependencies = [ "dirs-sys 0.4.1", ] [[package]] name = "dirs-sys" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" dependencies = [ "libc", "redox_users", "winapi", ] [[package]] name = "dirs-sys" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" dependencies = [ "libc", "option-ext", "redox_users", "windows-sys 0.48.0", ] [[package]] name = "displaydoc" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", "syn 2.0.100", ] [[package]] name = "doc-comment" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" dependencies = [ "libc", "windows-sys 0.59.0", ] [[package]] name = "error-code" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64f18991e7bf11e7ffee451b5318b5c1a73c52d0d0ada6e5a3017c8c1ced6a21" dependencies = [ "libc", "str-buf", ] [[package]] name = "exr" version = "1.73.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f83197f59927b46c04a183a619b7c29df34e63e63c7869320862268c0ef687e0" dependencies = [ "bit_field", "half", "lebe", "miniz_oxide", "rayon-core", "smallvec", "zune-inflate", ] [[package]] name = "fallible-iterator" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" [[package]] name = "fallible-streaming-iterator" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" [[package]] name = "fancy-regex" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b95f7c0680e4142284cf8b22c14a476e87d61b004a3a0861872b32ef7ead40a2" dependencies = [ "bit-set", "regex", ] [[package]] name = "fastrand" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fdeflate" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" dependencies = [ "simd-adler32", ] [[package]] name = "file-size" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9544f10105d33957765016b8a9baea7e689bf1f0f2f32c2fa2f568770c38d2b3" [[package]] name = "flate2" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" dependencies = [ "crc32fast", "miniz_oxide", ] [[package]] name = "flex-grow" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d504ae1bb01561686fa31189fe56754a5d869392890a1e64e69d198557a8eb78" [[package]] name = "float-cmp" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "fontconfig-parser" version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1fcfcd44ca6e90c921fee9fa665d530b21ef1327a4c1a6c5250ea44b776ada7" dependencies = [ "roxmltree", ] [[package]] name = "fontdb" version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3a6f9af55fb97ad673fb7a69533eb2f967648a06fa21f8c9bb2cd6d33975716" dependencies = [ "fontconfig-parser", "log", "memmap2", "slotmap", "tinyvec", "ttf-parser", ] [[package]] name = "form_urlencoded" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] [[package]] name = "gethostname" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb65d4ba3173c56a500b555b532f72c42e8d1fe64962b518897f8959fae2c177" dependencies = [ "libc", "winapi", ] [[package]] name = "getrandom" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", "wasi 0.11.0+wasi-snapshot-preview1", ] [[package]] name = "getrandom" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" dependencies = [ "cfg-if", "libc", "r-efi", "wasi 0.14.2+wasi-0.2.4", ] [[package]] name = "gif" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2" dependencies = [ "color_quant", "weezl", ] [[package]] name = "git2" version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5220b8ba44c68a9a7f7a7659e864dd73692e417ef0211bea133c7b74e031eeb9" dependencies = [ "bitflags 2.9.0", "libc", "libgit2-sys", "log", "url", ] [[package]] name = "glassbench" version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a637870281a348cf7a02967abfad0c807dabc7d461504c2176f37e8aa76a910" dependencies = [ "base64 0.13.1", "chrono", "csv2svg", "git2", "lazy_static", "open", "rusqlite", "serde", "serde_json", "tempfile", "termimad", "thiserror 1.0.69", ] [[package]] name = "glob" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] name = "half" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" dependencies = [ "cfg-if", "crunchy", ] [[package]] name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ "ahash", ] [[package]] name = "hashbrown" version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" dependencies = [ "allocator-api2", "equivalent", "foldhash", ] [[package]] name = "hashlink" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" dependencies = [ "hashbrown 0.14.5", ] [[package]] name = "heck" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "home" version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "iana-time-zone" version = "0.1.63" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "log", "wasm-bindgen", "windows-core", ] [[package]] name = "iana-time-zone-haiku" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ "cc", ] [[package]] name = "icu_collections" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" dependencies = [ "displaydoc", "yoke", "zerofrom", "zerovec", ] [[package]] name = "icu_locid" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" dependencies = [ "displaydoc", "litemap", "tinystr", "writeable", "zerovec", ] [[package]] name = "icu_locid_transform" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" dependencies = [ "displaydoc", "icu_locid", "icu_locid_transform_data", "icu_provider", "tinystr", "zerovec", ] [[package]] name = "icu_locid_transform_data" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" [[package]] name = "icu_normalizer" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" dependencies = [ "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", "icu_provider", "smallvec", "utf16_iter", "utf8_iter", "write16", "zerovec", ] [[package]] name = "icu_normalizer_data" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" [[package]] name = "icu_properties" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" dependencies = [ "displaydoc", "icu_collections", "icu_locid_transform", "icu_properties_data", "icu_provider", "tinystr", "zerovec", ] [[package]] name = "icu_properties_data" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" [[package]] name = "icu_provider" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" dependencies = [ "displaydoc", "icu_locid", "icu_provider_macros", "stable_deref_trait", "tinystr", "writeable", "yoke", "zerofrom", "zerovec", ] [[package]] name = "icu_provider_macros" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", "syn 2.0.100", ] [[package]] name = "id-arena" version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" [[package]] name = "idna" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" dependencies = [ "idna_adapter", "smallvec", "utf8_iter", ] [[package]] name = "idna_adapter" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" dependencies = [ "icu_normalizer", "icu_properties", ] [[package]] name = "image" version = "0.25.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db35664ce6b9810857a38a906215e75a9c879f0696556a39f59c62829710251a" dependencies = [ "bytemuck", "byteorder-lite", "color_quant", "exr", "gif", "image-webp 0.2.1", "num-traits", "png", "qoi", "ravif", "rayon", "rgb", "tiff", "zune-core", "zune-jpeg", ] [[package]] name = "image-webp" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f79afb8cbee2ef20f59ccd477a218c12a93943d075b492015ecb1bb81f8ee904" dependencies = [ "byteorder-lite", "quick-error", ] [[package]] name = "image-webp" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b77d01e822461baa8409e156015a1d91735549f0f2c17691bd2d996bef238f7f" dependencies = [ "byteorder-lite", "quick-error", ] [[package]] name = "imagesize" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285" [[package]] name = "imgref" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0263a3d970d5c054ed9312c0057b4f3bde9c0b33836d3637361d4a9e6e7a408" [[package]] name = "include_dir" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd" dependencies = [ "include_dir_macros", ] [[package]] name = "include_dir_macros" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75" dependencies = [ "proc-macro2", "quote", ] [[package]] name = "indexmap" version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", "hashbrown 0.15.2", ] [[package]] name = "interpolate_name" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" dependencies = [ "proc-macro2", "quote", "syn 2.0.100", ] [[package]] name = "is_executable" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4a1b5bad6f9072935961dfbf1cced2f3d129963d091b6f69f007fe04e758ae2" dependencies = [ "winapi", ] [[package]] name = "is_terminal_polyfill" version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itertools" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" dependencies = [ "either", ] [[package]] name = "itoa" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jobserver" version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" dependencies = [ "getrandom 0.3.2", "libc", ] [[package]] name = "jpeg-decoder" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" [[package]] name = "js-sys" version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ "once_cell", "wasm-bindgen", ] [[package]] name = "kurbo" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89234b2cc610a7dd927ebde6b41dd1a5d4214cffaef4cf1fb2195d592f92518f" dependencies = [ "arrayvec", "smallvec", ] [[package]] name = "lazy-regex" version = "3.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60c7310b93682b36b98fa7ea4de998d3463ccbebd94d935d6b48ba5b6ffa7126" dependencies = [ "lazy-regex-proc_macros", "once_cell", "regex", ] [[package]] name = "lazy-regex-proc_macros" version = "3.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ba01db5ef81e17eb10a5e0f2109d1b3a3e29bac3070fdbd7d156bf7dbd206a1" dependencies = [ "proc-macro2", "quote", "regex", "syn 2.0.100", ] [[package]] name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "lebe" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" [[package]] name = "lfs-core" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75aa9f1a56e1178a04270bdd6f28b3a9cc34bc5429a3139cd03e2eae2ecb455a" dependencies = [ "lazy-regex", "libc", "snafu", ] [[package]] name = "libc" version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "libfuzzer-sys" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf78f52d400cf2d84a3a973a78a592b4adc535739e0a5597a0da6f0c357adc75" dependencies = [ "arbitrary", "cc", ] [[package]] name = "libgit2-sys" version = "0.18.1+1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1dcb20f84ffcdd825c7a311ae347cce604a6f084a767dec4a4929829645290e" dependencies = [ "cc", "libc", "libz-sys", "pkg-config", ] [[package]] name = "libm" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" [[package]] name = "libredox" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ "bitflags 2.9.0", "libc", ] [[package]] name = "libsqlite3-sys" version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" dependencies = [ "cc", "pkg-config", "vcpkg", ] [[package]] name = "libz-sys" version = "1.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b70e7a7df205e92a1a4cd9aaae7898dac0aa555503cc0a649494d0d60e7651d" dependencies = [ "cc", "libc", "pkg-config", "vcpkg", ] [[package]] name = "linked-hash-map" version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "linux-raw-sys" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" [[package]] name = "litemap" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" [[package]] name = "lock_api" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", ] [[package]] name = "log" version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "loop9" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" dependencies = [ "imgref", ] [[package]] name = "lru" version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ "hashbrown 0.15.2", ] [[package]] name = "malloc_buf" version = "0.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" dependencies = [ "libc", ] [[package]] name = "maybe-rayon" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" dependencies = [ "cfg-if", "rayon", ] [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "memmap2" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" dependencies = [ "libc", ] [[package]] name = "memoffset" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" dependencies = [ "autocfg", ] [[package]] name = "minimad" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9c5d708226d186590a7b6d4a9780e2bdda5f689e0d58cd17012a298efd745d2" dependencies = [ "once_cell", ] [[package]] name = "minimal-lexical" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" dependencies = [ "adler2", "simd-adler32", ] [[package]] name = "mio" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ "libc", "log", "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.52.0", ] [[package]] name = "new_debug_unreachable" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" [[package]] name = "nix" version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" dependencies = [ "bitflags 1.3.2", "cfg-if", "libc", "memoffset", ] [[package]] name = "nix" version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ "bitflags 2.9.0", "cfg-if", "cfg_aliases", "libc", ] [[package]] name = "nom" version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ "memchr", "minimal-lexical", ] [[package]] name = "noop_proc_macro" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" [[package]] name = "normpath" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8911957c4b1549ac0dc74e30db9c8b0e66ddcd6d7acc33098f4c63a64a6d7ed" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "num-bigint" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ "num-integer", "num-traits", ] [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "num-derive" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", "syn 2.0.100", ] [[package]] name = "num-integer" version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ "num-traits", ] [[package]] name = "num-rational" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" dependencies = [ "num-bigint", "num-integer", "num-traits", ] [[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] [[package]] name = "objc" version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" dependencies = [ "malloc_buf", ] [[package]] name = "objc-sys" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" [[package]] name = "objc2" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" dependencies = [ "objc-sys", "objc2-encode", ] [[package]] name = "objc2-app-kit" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" dependencies = [ "bitflags 2.9.0", "block2", "libc", "objc2", "objc2-core-data", "objc2-core-image", "objc2-foundation", "objc2-quartz-core", ] [[package]] name = "objc2-core-data" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" dependencies = [ "bitflags 2.9.0", "block2", "objc2", "objc2-foundation", ] [[package]] name = "objc2-core-image" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" dependencies = [ "block2", "objc2", "objc2-foundation", "objc2-metal", ] [[package]] name = "objc2-encode" version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" [[package]] name = "objc2-foundation" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ "bitflags 2.9.0", "block2", "libc", "objc2", ] [[package]] name = "objc2-metal" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" dependencies = [ "bitflags 2.9.0", "block2", "objc2", "objc2-foundation", ] [[package]] name = "objc2-quartz-core" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" dependencies = [ "bitflags 2.9.0", "block2", "objc2", "objc2-foundation", "objc2-metal", ] [[package]] name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "open" version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcea7a30d6b81a2423cc59c43554880feff7b57d12916f231a79f8d6d9470201" dependencies = [ "pathdiff", "winapi", ] [[package]] name = "opener" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c62dcb6174f9cb326eac248f07e955d5d559c272730b6c03e396b443b562788" dependencies = [ "bstr", "normpath", "winapi", ] [[package]] name = "option-ext" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] name = "parking_lot" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", "windows-targets 0.52.6", ] [[package]] name = "paste" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "pathdiff" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" [[package]] name = "percent-encoding" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "phf" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" dependencies = [ "phf_macros", "phf_shared", ] [[package]] name = "phf_generator" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ "phf_shared", "rand", ] [[package]] name = "phf_macros" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" dependencies = [ "phf_generator", "phf_shared", "proc-macro2", "quote", "syn 2.0.100", ] [[package]] name = "phf_shared" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" dependencies = [ "siphasher", ] [[package]] name = "pico-args" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" [[package]] name = "pkg-config" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "plist" version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eac26e981c03a6e53e0aee43c113e3202f5581d5360dae7bd2c70e800dd0451d" dependencies = [ "base64 0.22.1", "indexmap", "quick-xml", "serde", "time", ] [[package]] name = "png" version = "0.17.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" dependencies = [ "bitflags 1.3.2", "crc32fast", "fdeflate", "flate2", "miniz_oxide", ] [[package]] name = "powerfmt" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ "zerocopy 0.8.24", ] [[package]] name = "proc-macro2" version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] [[package]] name = "proc-status" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0e0c0ac915e7b76b47850ba4ffc377abde6c6ff9eeace61d0a89623db449712" dependencies = [ "thiserror 1.0.69", ] [[package]] name = "profiling" version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "afbdc74edc00b6f6a218ca6a5364d6226a259d4b8ea1af4a0ea063f27e179f4d" dependencies = [ "profiling-procmacros", ] [[package]] name = "profiling-procmacros" version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a65f2e60fbf1063868558d69c6beacf412dc755f9fc020f514b7955fc914fe30" dependencies = [ "quote", "syn 2.0.100", ] [[package]] name = "qoi" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" dependencies = [ "bytemuck", ] [[package]] name = "quick-error" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quick-xml" version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d3a6e5838b60e0e8fa7a43f22ade549a37d61f8bdbe636d0d7816191de969c2" dependencies = [ "memchr", ] [[package]] name = "quote" version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] name = "r-efi" version = "5.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" [[package]] name = "rand" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", "rand_core", ] [[package]] name = "rand_chacha" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", "rand_core", ] [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom 0.2.15", ] [[package]] name = "rav1e" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd87ce80a7665b1cce111f8a16c1f3929f6547ce91ade6addf4ec86a8dda5ce9" dependencies = [ "arbitrary", "arg_enum_proc_macro", "arrayvec", "av1-grain", "bitstream-io", "built", "cfg-if", "interpolate_name", "itertools", "libc", "libfuzzer-sys", "log", "maybe-rayon", "new_debug_unreachable", "noop_proc_macro", "num-derive", "num-traits", "once_cell", "paste", "profiling", "rand", "rand_chacha", "simd_helpers", "system-deps", "thiserror 1.0.69", "v_frame", "wasm-bindgen", ] [[package]] name = "ravif" version = "0.11.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6a5f31fcf7500f9401fea858ea4ab5525c99f2322cfcee732c0e6c74208c0c6" dependencies = [ "avif-serialize", "imgref", "loop9", "quick-error", "rav1e", "rayon", "rgb", ] [[package]] name = "rayon" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ "either", "rayon-core", ] [[package]] name = "rayon-core" version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" dependencies = [ "crossbeam-deque", "crossbeam-utils", ] [[package]] name = "redox_syscall" version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" dependencies = [ "bitflags 2.9.0", ] [[package]] name = "redox_users" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ "getrandom 0.2.15", "libredox", "thiserror 1.0.69", ] [[package]] name = "regex" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "resvg" version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a325d5e8d1cebddd070b13f44cec8071594ab67d1012797c121f27a669b7958" dependencies = [ "gif", "image-webp 0.1.3", "log", "pico-args", "rgb", "svgtypes", "tiny-skia", "usvg", "zune-jpeg", ] [[package]] name = "rgb" version = "0.8.50" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" dependencies = [ "bytemuck", ] [[package]] name = "roff" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88f8660c1ff60292143c98d08fc6e2f654d722db50410e3f3797d40baaf9d8f3" [[package]] name = "roxmltree" version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" [[package]] name = "rusqlite" version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7753b721174eb8ff87a9a0e799e2d7bc3749323e773db92e0984debb00019d6e" dependencies = [ "bitflags 2.9.0", "fallible-iterator", "fallible-streaming-iterator", "hashlink", "libsqlite3-sys", "smallvec", ] [[package]] name = "rust-fuzzy-search" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a157657054ffe556d8858504af8a672a054a6e0bd9e8ee531059100c0fa11bb2" [[package]] name = "rustc-hash" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] name = "rustix" version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ "bitflags 2.9.0", "errno", "libc", "linux-raw-sys 0.4.15", "windows-sys 0.59.0", ] [[package]] name = "rustix" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" dependencies = [ "bitflags 2.9.0", "errno", "libc", "linux-raw-sys 0.9.4", "windows-sys 0.59.0", ] [[package]] name = "rustversion" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" [[package]] name = "rustybuzz" version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c85d1ccd519e61834798eb52c4e886e8c2d7d698dd3d6ce0b1b47eb8557f1181" dependencies = [ "bitflags 2.9.0", "bytemuck", "core_maths", "log", "smallvec", "ttf-parser", "unicode-bidi-mirroring", "unicode-ccc", "unicode-properties", "unicode-script", ] [[package]] name = "ryu" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "same-file" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ "winapi-util", ] [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "secular" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3dc3eccdf599b53eba8a34a1190bd47394948258d1c43dca9cceb2426e25bb5" dependencies = [ "unicode-normalization", ] [[package]] name = "serde" version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", "syn 2.0.100", ] [[package]] name = "serde_json" version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ "itoa", "memchr", "ryu", "serde", ] [[package]] name = "serde_spanned" version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" dependencies = [ "serde", ] [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" dependencies = [ "libc", "signal-hook-registry", ] [[package]] name = "signal-hook-mio" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" dependencies = [ "libc", "mio", "signal-hook", ] [[package]] name = "signal-hook-registry" version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ "libc", ] [[package]] name = "simd-adler32" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" [[package]] name = "simd_helpers" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" dependencies = [ "quote", ] [[package]] name = "simplecss" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a9c6883ca9c3c7c90e888de77b7a5c849c779d25d74a1269b0218b14e8b136c" dependencies = [ "log", ] [[package]] name = "siphasher" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] name = "slotmap" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" dependencies = [ "version_check", ] [[package]] name = "smallvec" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" [[package]] name = "snafu" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4de37ad025c587a29e8f3f5605c00f70b98715ef90b9061a815b9e59e9042d6" dependencies = [ "doc-comment", "snafu-derive", ] [[package]] name = "snafu-derive" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "990079665f075b699031e9c08fd3ab99be5029b96f3b78dc0709e8f77e4efebf" dependencies = [ "heck 0.4.1", "proc-macro2", "quote", "syn 1.0.109", ] [[package]] name = "splitty" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2db70a1e6827e4d71c655b606caf1346862c38ae52ab4f58c32635e7c7aedd67" [[package]] name = "stable_deref_trait" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "str-buf" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0" [[package]] name = "strict" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "991af58f8bd0512b0c76abc87f8f6a8a492c314ebcd13189b426c00c95f6f0ee" [[package]] name = "strict" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f42444fea5b87a39db4218d9422087e66a85d0e7a0963a439b07bcdf91804006" [[package]] name = "strict-num" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" dependencies = [ "float-cmp", ] [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "svg" version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "583e1c5c326fd6fede8797006de3b95ad6bcd60a592952952c5ba7ddd7e84c83" [[package]] name = "svgtypes" version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68c7541fff44b35860c1a7a47a7cadf3e4a304c457b58f9870d9706ece028afc" dependencies = [ "kurbo", "siphasher", ] [[package]] name = "syn" version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "syn" version = "2.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "synstructure" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", "syn 2.0.100", ] [[package]] name = "syntect-no-panic" version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5a48fbacf5de9abade2f665fba57c35d1297e9c7c10dbc906873589de3ca4c6" dependencies = [ "bincode", "bitflags 1.3.2", "fancy-regex", "flate2", "fnv", "once_cell", "plist", "regex-syntax", "serde", "serde_derive", "serde_json", "thiserror 1.0.69", "walkdir", "yaml-rust", ] [[package]] name = "system-deps" version = "6.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" dependencies = [ "cfg-expr", "heck 0.5.0", "pkg-config", "toml", "version-compare", ] [[package]] name = "target-lexicon" version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tempfile" version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" dependencies = [ "fastrand", "getrandom 0.3.2", "once_cell", "rustix 1.0.5", "windows-sys 0.59.0", ] [[package]] name = "termimad" version = "0.31.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7301d9c2c4939c97f25376b70d3c13311f8fefdee44092fc361d2a98adc2cbb6" dependencies = [ "coolor", "crokey", "crossbeam", "lazy-regex", "minimad", "serde", "thiserror 2.0.12", "unicode-width", ] [[package]] name = "terminal-clipboard" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e0fd8cb5cf744b501e657eb27df7909ff917eacbfee34bc4bb13d4e6411a131" dependencies = [ "clipboard-win", "clipboard_macos", "once_cell", "termux-clipboard", "x11-clipboard", ] [[package]] name = "terminal-light" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a9474484d1a0c031cd7d065c6f027a376859c9fedb32c94df3d7a797218bbb7" dependencies = [ "coolor", "crossterm", "thiserror 1.0.69", "xterm-query", ] [[package]] name = "termux-clipboard" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f6aff13ca3293315b94f6dbd9c69e1c958fe421c294681e2ffda80c9858e36f" [[package]] name = "thiserror" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl 1.0.69", ] [[package]] name = "thiserror" version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" dependencies = [ "thiserror-impl 2.0.12", ] [[package]] name = "thiserror-impl" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", "syn 2.0.100", ] [[package]] name = "thiserror-impl" version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", "syn 2.0.100", ] [[package]] name = "tiff" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e" dependencies = [ "flate2", "jpeg-decoder", "weezl", ] [[package]] name = "time" version = "0.3.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" dependencies = [ "deranged", "itoa", "num-conv", "powerfmt", "serde", "time-core", "time-macros", ] [[package]] name = "time-core" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" [[package]] name = "time-macros" version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" dependencies = [ "num-conv", "time-core", ] [[package]] name = "tiny-skia" version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" dependencies = [ "arrayref", "arrayvec", "bytemuck", "cfg-if", "log", "png", "tiny-skia-path", ] [[package]] name = "tiny-skia-path" version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" dependencies = [ "arrayref", "bytemuck", "strict-num", ] [[package]] name = "tinystr" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" dependencies = [ "displaydoc", "zerovec", ] [[package]] name = "tinyvec" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" dependencies = [ "tinyvec_macros", ] [[package]] name = "tinyvec_macros" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "toml" version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" dependencies = [ "serde", "serde_spanned", "toml_datetime", "toml_edit", ] [[package]] name = "toml_datetime" version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" dependencies = [ "serde", ] [[package]] name = "toml_edit" version = "0.22.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", "winnow", ] [[package]] name = "trash" version = "3.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c658458d46d9d5a153a3b5cdd88d8579ad50d4fb85d53961e4526c8fc7c55a57" dependencies = [ "chrono", "libc", "log", "objc", "once_cell", "scopeguard", "url", "windows", ] [[package]] name = "ttf-parser" version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5be21190ff5d38e8b4a2d3b6a3ae57f612cc39c96e83cedeaf7abc338a8bac4a" dependencies = [ "core_maths", ] [[package]] name = "umask" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec9a46c2549e35c054e0ffe281a3a6ec0007793db4df106604d37ed3f4d73d1c" dependencies = [ "thiserror 1.0.69", ] [[package]] name = "unicode-bidi" version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" [[package]] name = "unicode-bidi-mirroring" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64af057ad7466495ca113126be61838d8af947f41d93a949980b2389a118082f" [[package]] name = "unicode-ccc" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "260bc6647b3893a9a90668360803a15f96b85a5257b1c3a0c3daf6ae2496de42" [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "unicode-normalization" version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] [[package]] name = "unicode-properties" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" [[package]] name = "unicode-script" version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fb421b350c9aff471779e262955939f565ec18b86c15364e6bdf0d662ca7c1f" [[package]] name = "unicode-vo" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" [[package]] name = "unicode-width" version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "url" version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", "percent-encoding", ] [[package]] name = "usvg" version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7447e703d7223b067607655e625e0dbca80822880248937da65966194c4864e6" dependencies = [ "base64 0.22.1", "data-url", "flate2", "fontdb", "imagesize", "kurbo", "log", "pico-args", "roxmltree", "rustybuzz", "simplecss", "siphasher", "strict-num", "svgtypes", "tiny-skia-path", "unicode-bidi", "unicode-script", "unicode-vo", "xmlwriter", ] [[package]] name = "utf16_iter" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" [[package]] name = "utf8_iter" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "utf8parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uzers" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4df81ff504e7d82ad53e95ed1ad5b72103c11253f39238bcc0235b90768a97dd" dependencies = [ "libc", "log", ] [[package]] name = "v_frame" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6f32aaa24bacd11e488aa9ba66369c7cd514885742c9fe08cfe85884db3e92b" dependencies = [ "aligned-vec", "num-traits", "wasm-bindgen", ] [[package]] name = "vcpkg" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version-compare" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "walkdir" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", ] [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasi" version = "0.14.2+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" dependencies = [ "wit-bindgen-rt", ] [[package]] name = "wasm-bindgen" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", "proc-macro2", "quote", "syn 2.0.100", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", "syn 2.0.100", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" dependencies = [ "unicode-ident", ] [[package]] name = "weezl" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" [[package]] name = "which" version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" dependencies = [ "either", "home", "once_cell", "rustix 0.38.44", ] [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "winapi-wsapoll" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1eafc5f679c576995526e81635d0cf9695841736712b4e892f87abbe6fed3f28" dependencies = [ "winapi", ] [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e745dab35a0c4c77aa3ce42d595e13d2003d6902d6b08c9ef5fc326d08da12b" dependencies = [ "windows-targets 0.42.2", ] [[package]] name = "windows-core" version = "0.61.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" dependencies = [ "windows-implement", "windows-interface", "windows-link", "windows-result", "windows-strings", ] [[package]] name = "windows-implement" version = "0.60.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", "syn 2.0.100", ] [[package]] name = "windows-interface" version = "0.59.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", "syn 2.0.100", ] [[package]] name = "windows-link" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" [[package]] name = "windows-result" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" dependencies = [ "windows-link", ] [[package]] name = "windows-strings" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" dependencies = [ "windows-link", ] [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ "windows-targets 0.48.5", ] [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-targets" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" dependencies = [ "windows_aarch64_gnullvm 0.42.2", "windows_aarch64_msvc 0.42.2", "windows_i686_gnu 0.42.2", "windows_i686_msvc 0.42.2", "windows_x86_64_gnu 0.42.2", "windows_x86_64_gnullvm 0.42.2", "windows_x86_64_msvc 0.42.2", ] [[package]] name = "windows-targets" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm 0.48.5", "windows_aarch64_msvc 0.48.5", "windows_i686_gnu 0.48.5", "windows_i686_msvc 0.48.5", "windows_x86_64_gnu 0.48.5", "windows_x86_64_gnullvm 0.48.5", "windows_x86_64_msvc 0.48.5", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63d3fcd9bba44b03821e7d699eeee959f3126dcc4aa8e4ae18ec617c2a5cea10" dependencies = [ "memchr", ] [[package]] name = "wit-bindgen-rt" version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ "bitflags 2.9.0", ] [[package]] name = "write16" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" [[package]] name = "writeable" version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" [[package]] name = "x11-clipboard" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b41aca1115b1f195f21c541c5efb423470848d48143127d0f07f8b90c27440df" dependencies = [ "x11rb", ] [[package]] name = "x11rb" version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1641b26d4dec61337c35a1b1aaf9e3cba8f46f0b43636c609ab0291a648040a" dependencies = [ "gethostname", "nix 0.26.4", "winapi", "winapi-wsapoll", "x11rb-protocol", ] [[package]] name = "x11rb-protocol" version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82d6c3f9a0fb6701fab8f6cea9b0c0bd5d6876f1f89f7fada07e558077c344bc" dependencies = [ "nix 0.26.4", ] [[package]] name = "xmlwriter" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" [[package]] name = "xterm-query" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "292c33df434fde4ecd87a7afecdfa1681a3d29567fc69c774a0d83d32c095331" dependencies = [ "nix 0.29.0", "thiserror 1.0.69", ] [[package]] name = "yaml-rust" version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" dependencies = [ "linked-hash-map", ] [[package]] name = "yoke" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" dependencies = [ "serde", "stable_deref_trait", "yoke-derive", "zerofrom", ] [[package]] name = "yoke-derive" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", "syn 2.0.100", "synstructure", ] [[package]] name = "zerocopy" version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "zerocopy-derive 0.7.35", ] [[package]] name = "zerocopy" version = "0.8.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" dependencies = [ "zerocopy-derive 0.8.24", ] [[package]] name = "zerocopy-derive" version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", "syn 2.0.100", ] [[package]] name = "zerocopy-derive" version = "0.8.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" dependencies = [ "proc-macro2", "quote", "syn 2.0.100", ] [[package]] name = "zerofrom" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", "syn 2.0.100", "synstructure", ] [[package]] name = "zerovec" version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" dependencies = [ "yoke", "zerofrom", "zerovec-derive", ] [[package]] name = "zerovec-derive" version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", "syn 2.0.100", ] [[package]] name = "zune-core" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" [[package]] name = "zune-inflate" version = "0.2.54" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" dependencies = [ "simd-adler32", ] [[package]] name = "zune-jpeg" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99a5bab8d7dedf81405c4bb1f2b83ea057643d9cb28778cea9eecddeedd2e028" dependencies = [ "zune-core", ] broot-1.46.3/Cargo.toml0000644000000104250000000000100102540ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.79" name = "broot" version = "1.46.3" authors = ["dystroy "] build = "build.rs" exclude = [ "website", "broot*.zip", ] autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "File browser and launcher" homepage = "https://dystroy.org/broot" documentation = "https://dystroy.org/broot" readme = "README.md" keywords = [ "cli", "fuzzy", "tree", "search", "file", ] categories = ["command-line-utilities"] license = "MIT" repository = "https://github.com/Canop/broot" [features] clipboard = ["terminal-clipboard"] default = [] kitty-csi-check = ["xterm-query"] trash = ["dep:trash"] [lib] name = "broot" path = "src/lib.rs" [[bin]] name = "broot" path = "src/main.rs" [[test]] name = "search_strings" path = "tests/search_strings.rs" [[bench]] name = "composite" path = "benches/composite.rs" harness = false [[bench]] name = "fuzzy" path = "benches/fuzzy.rs" harness = false [[bench]] name = "path_normalization" path = "benches/path_normalization.rs" harness = false [[bench]] name = "toks" path = "benches/toks.rs" harness = false [dependencies.ansi_colours] version = "1.2" [dependencies.base64] version = "0.21" [dependencies.bet] version = "1.0.4" [dependencies.char_reader] version = "0.1" [dependencies.chrono] version = "0.4" [dependencies.clap] version = "4.4" features = [ "derive", "cargo", ] [dependencies.clap-help] version = "1.3" [dependencies.cli-log] version = "2.1" [dependencies.crokey] version = "1.1" [dependencies.custom_error] version = "1.6" [dependencies.deser-hjson] version = "2.2.3" [dependencies.directories] version = "4.0" [dependencies.file-size] version = "1.0.3" [dependencies.flex-grow] version = "0.1" [dependencies.git2] version = "0.20" default-features = false [dependencies.glob] version = "0.3" [dependencies.id-arena] version = "2.2.1" [dependencies.image] version = "0.25" [dependencies.include_dir] version = "0.7" [dependencies.lazy-regex] version = "3.4" [dependencies.libc] version = "0.2" [dependencies.lru] version = "0.12" [dependencies.memmap2] version = "0.9" [dependencies.once_cell] version = "1.18" [dependencies.opener] version = "0.6" [dependencies.pathdiff] version = "0.2" [dependencies.phf] version = "0.11" features = ["macros"] [dependencies.rayon] version = "1.9" [dependencies.resvg] version = "0.44" [dependencies.rustc-hash] version = "2" [dependencies.secular] version = "1.0" features = [ "normalization", "bmp", ] [dependencies.serde] version = "1.0" features = ["derive"] [dependencies.smallvec] version = "1.11" [dependencies.splitty] version = "1.0.2" [dependencies.strict] version = "0.1.4" [dependencies.syntect] version = "6.0" features = ["default-fancy"] default-features = false package = "syntect-no-panic" [dependencies.tempfile] version = "3.2" [dependencies.termimad] version = "0.31" [dependencies.terminal-clipboard] version = "0.4.1" optional = true [dependencies.terminal-light] version = "1.7" [dependencies.toml] version = "0.8" [dependencies.trash] version = "3.1.2" optional = true [dependencies.umask] version = "2.1.0" [dependencies.unicode-width] version = "0.1.10" [dependencies.which] version = "4.4.0" [dependencies.xterm-query] version = "0.5" optional = true [dev-dependencies.glassbench] version = "0.4.4" [build-dependencies.clap] version = "4.4" features = [ "derive", "cargo", ] [build-dependencies.clap_complete] version = "4.4" [build-dependencies.clap_mangen] version = "0.2.12" [target."cfg(unix)".dependencies.lfs-core] version = "0.11.0" [target."cfg(unix)".dependencies.uzers] version = "0.12" [target."cfg(windows)".dependencies.is_executable] version = "1.0.1" [profile.dev] debug = 0 [profile.release] lto = "fat" codegen-units = 1 debug = 0 strip = "symbols" broot-1.46.3/Cargo.toml.orig0000644000000067610000000000100112230ustar [package] name = "broot" version = "1.46.3" authors = ["dystroy "] repository = "https://github.com/Canop/broot" homepage = "https://dystroy.org/broot" documentation = "https://dystroy.org/broot" description = "File browser and launcher" edition = "2021" keywords = ["cli", "fuzzy", "tree", "search", "file"] license = "MIT" categories = ["command-line-utilities"] readme = "README.md" build = "build.rs" rust-version = "1.79" exclude = ["website", "broot*.zip"] [features] default = [] clipboard = ["terminal-clipboard"] kitty-csi-check = ["xterm-query"] trash = ["dep:trash"] [dependencies] ansi_colours = "1.2" base64 = "0.21" bet = "1.0.4" char_reader = "0.1" chrono = "0.4" clap = { version = "4.4", features = ["derive", "cargo"] } clap-help = "1.3" cli-log = "2.1" crokey = "1.1" custom_error = "1.6" deser-hjson = "2.2.3" directories = "4.0" file-size = "1.0.3" flex-grow = "0.1" git2 = { version = "0.20", default-features = false } # waiting for a good pure-rust alternative glob = "0.3" id-arena = "2.2.1" image = "0.25" include_dir = "0.7" lazy-regex = "3.4" libc = "0.2" lru = "0.12" memmap2 = "0.9" once_cell = "1.18" # waiting for https://github.com/rust-lang/rust/issues/109736 opener = "0.6" pathdiff = "0.2" phf = { version = "0.11", features = ["macros"] } rayon = "1.9" resvg = "0.44" rustc-hash = "2" secular = { version = "1.0", features = ["normalization", "bmp"] } serde = { version = "1.0", features = ["derive"] } smallvec = "1.11" # version 2 is still alpha splitty = "1.0.2" strict = "0.1.4" syntect = { package = "syntect-no-panic", version = "6.0", default-features = false, features = ["default-fancy"] } # see https://github.com/Canop/broot/pull/968 tempfile = "3.2" termimad = "0.31" terminal-clipboard = { version = "0.4.1", optional = true } terminal-light = "1.7" toml = "0.8" trash = { version = "3.1.2", optional = true } umask = "2.1.0" unicode-width = "0.1.10" which = "4.4.0" xterm-query = { version = "0.5", optional = true } [dev-dependencies] glassbench = "0.4.4" [target.'cfg(unix)'.dependencies] lfs-core = "0.11.0" uzers = "0.12" [target.'cfg(windows)'.dependencies] is_executable = "1.0.1" [build-dependencies] clap = { version = "4.4", features = ["derive", "cargo"] } clap_complete = "4.4" clap_mangen = "0.2.12" [profile.dev] debug = false [profile.release] debug = false lto = "fat" codegen-units = 1 # this removes a few hundred bytes from the final exec size strip = "symbols" [[bench]] name = "fuzzy" harness = false [[bench]] name = "toks" harness = false [[bench]] name = "composite" harness = false [[bench]] name = "path_normalization" harness = false [patch.crates-io] # bet = { path = "../bet" } # clap-help = { path = "../clap-help" } # cli-log = { path = "../cli-log" } # coolor = { path = "../coolor" } # crossterm = { path = "../crossterm-rs/crossterm" } # csv2svg = { path = "../csv2svg" } # deser-hjson = { path = "../deser-hjson" } # glassbench = { path = "../glassbench" } # lfs-core = { path = "../lfs-core" } # minimad = { path = "../minimad" } # secular = { path = "../secular", features=["normalization"] } # syntect-no-panic = { path = "../syntect" } # termimad = { path = "../termimad" } # terminal-clipboard = { path = "../terminal-clipboard" } # terminal-light = { path = "../terminal-light" } # umask = { path = "../umask" } # crokey = { path = "../crokey" } # lazy-regex = { path = "../lazy-regex" } # lazy-regex-proc_macros = { path = "../lazy-regex/src/proc_macros" } # strict = { path = "../strict" } # xterm-query = { path = "../xterm-query" } broot-1.46.3/Cargo.toml.orig000064400000000000000000000067611046102023000137450ustar 00000000000000[package] name = "broot" version = "1.46.3" authors = ["dystroy "] repository = "https://github.com/Canop/broot" homepage = "https://dystroy.org/broot" documentation = "https://dystroy.org/broot" description = "File browser and launcher" edition = "2021" keywords = ["cli", "fuzzy", "tree", "search", "file"] license = "MIT" categories = ["command-line-utilities"] readme = "README.md" build = "build.rs" rust-version = "1.79" exclude = ["website", "broot*.zip"] [features] default = [] clipboard = ["terminal-clipboard"] kitty-csi-check = ["xterm-query"] trash = ["dep:trash"] [dependencies] ansi_colours = "1.2" base64 = "0.21" bet = "1.0.4" char_reader = "0.1" chrono = "0.4" clap = { version = "4.4", features = ["derive", "cargo"] } clap-help = "1.3" cli-log = "2.1" crokey = "1.1" custom_error = "1.6" deser-hjson = "2.2.3" directories = "4.0" file-size = "1.0.3" flex-grow = "0.1" git2 = { version = "0.20", default-features = false } # waiting for a good pure-rust alternative glob = "0.3" id-arena = "2.2.1" image = "0.25" include_dir = "0.7" lazy-regex = "3.4" libc = "0.2" lru = "0.12" memmap2 = "0.9" once_cell = "1.18" # waiting for https://github.com/rust-lang/rust/issues/109736 opener = "0.6" pathdiff = "0.2" phf = { version = "0.11", features = ["macros"] } rayon = "1.9" resvg = "0.44" rustc-hash = "2" secular = { version = "1.0", features = ["normalization", "bmp"] } serde = { version = "1.0", features = ["derive"] } smallvec = "1.11" # version 2 is still alpha splitty = "1.0.2" strict = "0.1.4" syntect = { package = "syntect-no-panic", version = "6.0", default-features = false, features = ["default-fancy"] } # see https://github.com/Canop/broot/pull/968 tempfile = "3.2" termimad = "0.31" terminal-clipboard = { version = "0.4.1", optional = true } terminal-light = "1.7" toml = "0.8" trash = { version = "3.1.2", optional = true } umask = "2.1.0" unicode-width = "0.1.10" which = "4.4.0" xterm-query = { version = "0.5", optional = true } [dev-dependencies] glassbench = "0.4.4" [target.'cfg(unix)'.dependencies] lfs-core = "0.11.0" uzers = "0.12" [target.'cfg(windows)'.dependencies] is_executable = "1.0.1" [build-dependencies] clap = { version = "4.4", features = ["derive", "cargo"] } clap_complete = "4.4" clap_mangen = "0.2.12" [profile.dev] debug = false [profile.release] debug = false lto = "fat" codegen-units = 1 # this removes a few hundred bytes from the final exec size strip = "symbols" [[bench]] name = "fuzzy" harness = false [[bench]] name = "toks" harness = false [[bench]] name = "composite" harness = false [[bench]] name = "path_normalization" harness = false [patch.crates-io] # bet = { path = "../bet" } # clap-help = { path = "../clap-help" } # cli-log = { path = "../cli-log" } # coolor = { path = "../coolor" } # crossterm = { path = "../crossterm-rs/crossterm" } # csv2svg = { path = "../csv2svg" } # deser-hjson = { path = "../deser-hjson" } # glassbench = { path = "../glassbench" } # lfs-core = { path = "../lfs-core" } # minimad = { path = "../minimad" } # secular = { path = "../secular", features=["normalization"] } # syntect-no-panic = { path = "../syntect" } # termimad = { path = "../termimad" } # terminal-clipboard = { path = "../terminal-clipboard" } # terminal-light = { path = "../terminal-light" } # umask = { path = "../umask" } # crokey = { path = "../crokey" } # lazy-regex = { path = "../lazy-regex" } # lazy-regex-proc_macros = { path = "../lazy-regex/src/proc_macros" } # strict = { path = "../strict" } # xterm-query = { path = "../xterm-query" } broot-1.46.3/LICENSE000064400000000000000000000020461046102023000120530ustar 00000000000000MIT License Copyright (c) 2018 Canop Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. broot-1.46.3/README.md000064400000000000000000000200061046102023000123210ustar 00000000000000## Broot [![Tests][s3]][l3] [![MIT][s2]][l2] [![Latest Version][s1]][l1] [![Chat on Miaou][s4]][l4] [![Packaging status][srep]][lrep] [s1]: https://img.shields.io/crates/v/broot.svg [l1]: https://crates.io/crates/broot [s2]: https://img.shields.io/badge/license-MIT-blue.svg [l2]: LICENSE [s3]: https://github.com/Canop/broot/actions/workflows/tests.yml/badge.svg [l3]: https://github.com/Canop/broot/actions/workflows/tests.yml [s4]: https://miaou.dystroy.org/static/shields/room.svg [l4]: https://miaou.dystroy.org/3490?broot [srep]: https://repology.org/badge/tiny-repos/broot.svg [lrep]: https://repology.org/project/broot/versions Broot is a better way to navigate directories, find files, and launch commands. ![cows](website/docs/img/20241027-cows.png) [**Complete Documentation**](https://dystroy.org/broot/) - [**Installation Instructions**](https://dystroy.org/broot/install/) - [**Contributing or Getting Help**](https://dystroy.org/blog/contributing/) ## Get an overview of a directory, even a big one Hit `br -s` ![overview](website/docs/img/20230930-overview.png) Notice the *unlisted*? That's what makes it usable where the old `tree` command would produce pages of output. `.gitignore` files are properly dealt with to put unwanted files out of your way. As you sometimes want to see gitignored files, or hidden ones, you'll soon get used to the alti and alth shortcuts to toggle those visibilities. (you can ignore them though, see [documentation](https://dystroy.org/broot/navigation/#toggles)). ## Find a directory then `cd` to it type a few letters ![cd](website/docs/img/20230930-cd.png) Hit altenter and you're back to the terminal in the desired location. This way, you can navigate to a directory with the minimum amount of keystrokes, even if you don't exactly remember where it is. Broot is fast and doesn't block (any keystroke interrupts the current search to start the next one). Most useful keys for this: * the letters of what you're looking for * enter on the root line to go up to the parent (staying in broot) * enter to focus a directory (staying in broot) * esc to get back to the previous state or clear your search * and may be used to move the selection * altenter to get back to the shell having `cd` to the selected directory * alth to toggle showing hidden files (the ones whose name starts with a dot) * alti to toggle showing gitignored files * `:q` if you just want to quit (you can use ctrlq if you prefer) ## Never lose track of file hierarchy while you search ![search](website/docs/img/20230930-gccran.png) Broot tries to select the most relevant file. You can still go from one match to another one using tab or arrow keys. You may also search with a regular expression. To do this, add a `/` before the pattern. And you have [other types of searches](input/#the-filtering-pattern), for example searching on file content (start with `c/`): ![content search](website/docs/img/20230930-content-memm.png) You may also apply logical operators or combine patterns, for example searching `test` in all files except json ones could be `!/json$/&c/test` and searching `carg` both in file names and file contents would be `carg|c/carg`. Once the file you want is selected you can * hit enter (or double-click) to open it in your system's default program * hit altenter to open it in your system's default program and close broot * hit ctrl to preview it (and then a second time to go inside the preview) * type a verb. For example `:e` opens the file in your preferred editor (which may be a terminal one) [blog: a broot content search workflow](https://dystroy.org/blog/broot-c-search/) ## Manipulate your files Most often, when not using broot, you move your files in the blind. You do a few `ls` before, then your manipulation, and maybe you check after. You can instead do it without losing the view of the file hierarchy. ![mv](website/docs/img/20230930-mv.png) Move, copy, rm, mkdir, are built in and you can add your own shortcuts. Here's chmod: ![chmod](website/docs/img/20230930-chmod.png) ## Manage files with panels When a directory is selected, do ctrl and you open another panel (you may open other ones, or navigate between them, with ctrl and ctrl). ![custom colors tree](website/docs/img/20230930-colored-panels.png) (yes, colors are fully customizable) You can for example copy or move elements between panels: ![cpp](website/docs/img/20230930-cpp.png) If you like you may do it Norton Commander style by binding `:copy_to_panel` to F5 and `:move_to_panel` to F6. ## Preview files Hit ctrl when a file is selected and the preview panel appears. ![preview](website/docs/img/20230930-preview.png) ![preview](website/docs/img/20230930-preview-image.png) The preview panel stays synchronized with the selection in tree panels. Broot displays images in high resolution when the terminal supports Kitty's graphics protocol (compatible terminals: [Kitty](https://sw.kovidgoyal.net/kitty/index.html), [WezTerm](https://wezfurlong.org/wezterm/)): ![kitty preview](website/docs/img/20201127-kitty-preview.png) ## Apply a standard or personal command to a file ![size](website/docs/img/20230930-edit.png) Just find the file you want to edit with a few keystrokes, type `:e`, then enter. You can add verbs or configure the existing ones; see [documentation](https://dystroy.org/broot/conf_file/#verbs-shortcuts-and-keys). And you can add shortcuts, for example a ctrl sequence or a function key ## Apply commands on several files Add files to the [staging area](staging-area) then execute any command on all of them. ![staging mv](website/docs/img/20230930-staging-mv.png) ## Replace `ls` (and its clones): If you want to display *sizes*, *dates* and *permissions*, do `br -sdp` which gets you this: ![replace ls](website/docs/img/20240501-sdp.png) You may also toggle options with a few keystrokes while inside broot. For example you could have typed this `-sdp` while in broot. Or hit alth and you see hidden files. ## Sort, see what takes space: You may sort by launching broot with `--sort-by-size` or `--sort-by-date`. Or you may, inside broot, type a space, then `sd`, and enter and you toggled the `:sort_by_date` mode. When sorting, the whole content of directories is taken into account. So if you want to find on Monday morning the most recently modified files, launch `br --sort-by-date ~`. If you start broot with the `--whale-spotting` option (or its shortcut `-w`), you get a mode tailored to "whale spotting" navigation, making it easy to determine what files or folders take space. Sizes, dates, files counts, are computed in the background, you don't have to wait for them when you navigate. ![size](website/docs/img/20230930-whale-spotting.png) And you keep all broot tools, like filtering or the ability to delete or open files and directories. If you hit `:fs`, you can check the usage of all filesystems, so that you focus on cleaning the full ones. ![fs](website/docs/img/20230930-fs.png) ## Check git statuses: Use `:gf` to display the statuses of files (what are the new ones, the modified ones, etc.), the current branch name and the change statistics. ![size](website/docs/img/20230930-git.png) And if you want to see *only* the files which would be displayed by the `git status` command, do `:gs`. From there it's easy to edit, or diff, selected files. ![gg](website/docs/img/20230930-gg.png) From there it's easy to edit, diff, or revert selected files. [blog: use broot and meld to diff before commit](https://dystroy.org/blog/gg/) ## Further Reading See **[Broot's web site](https://dystroy.org/broot)** for instructions regarding installation and usage. broot-1.46.3/bacon.toml000064400000000000000000000035621046102023000130310ustar 00000000000000# This is a configuration file for the bacon tool # More info at https://github.com/Canop/bacon default_job = "check" env.CARGO_TERM_COLOR = "always" [jobs] [jobs.check-all] command = ["cargo", "check", "--all-targets"] need_stdout = false watch = ["tests", "benches", "examples"] [jobs.bacon-ls] command = [ "cargo", "check", "--message-format", "json-diagnostic-rendered-ansi" ] analyzer = "cargo_json" need_stdout = true [exports.cargo-json-spans] auto = true exporter = "analyzer" line_format = "{diagnostic.level}:{span.file_name}:{span.line_start}:{span.line_end}:{diagnostic.message}" path = "bacon-analyzzzer.json" [jobs.check] command = [ "cargo", "check", "--features", "clipboard kitty-csi-check trash", ] need_stdout = false watch = ["benches"] [jobs.miri] command = ["cargo", "+nightly", "miri", "run"] need_stdout = true [jobs.win] command = ["cross", "build", "--target", "x86_64-pc-windows-gnu", "--release", "--features", "clipboard"] [jobs.light] command = ["cargo", "check"] need_stdout = false [jobs.clippy] command = [ "cargo", "clippy", "--", "-A", "clippy::bool_to_int_with_if", "-A", "clippy::collapsible_else_if", "-A", "clippy::collapsible_if", "-A", "clippy::derive_partial_eq_without_eq", "-A", "clippy::if_same_then_else", "-A", "clippy::len_without_is_empty", "-A", "clippy::manual_clamp", "-A", "clippy::manual_range_contains", "-A", "clippy::manual_unwrap_or", "-A", "clippy::match_like_matches_macro", "-A", "clippy::module_inception", "-A", "clippy::needless_bool", "-A", "clippy::needless_range_loop", "-A", "clippy::neg_multiply", "-A", "clippy::vec_init_then_push", "-W", "clippy::explicit_iter_loop", "-A", "clippy::unnecessary_map_or", ] need_stdout = false [jobs.test] command = ["cargo", "test"] need_stdout = true [keybindings] a = "job:check-all" i = "job:initial" c = "job:clippy" d = "job:doc-open" t = "job:test" r = "job:run" broot-1.46.3/benches/composite.rs000064400000000000000000000016311046102023000150240ustar 00000000000000mod shared; use { broot::{ command::CommandParts, pattern::*, }, glassbench::*, }; // this file benches composite patterns on file names so don't // use file content sub patterns here static PATTERNS: &[&str] = &[ "réveil", "r&!e", "(!e&!b)|c", ]; fn bench_score_of_composite(gb: &mut Bench) { let search_modes = SearchModeMap::default(); for pattern in PATTERNS { let name = format!("Composite({:?})::score_of", &pattern); gb.task(name, |b| { let parts = CommandParts::from(pattern.to_string()); let cp = Pattern::new(&parts.pattern, &search_modes, 10*1024*1024).unwrap(); b.iter(|| { for name in shared::NAMES { pretend_used(cp.score_of_string(name)); } }); }); } } glassbench!( "Composite Patterns", bench_score_of_composite, ); broot-1.46.3/benches/fuzzy.rs000064400000000000000000000011361046102023000142110ustar 00000000000000mod shared; use { broot::pattern::FuzzyPattern, glassbench::*, }; static PATTERNS: &[&str] = &["réveil", "AB", "e", "brt", "brootz"]; fn bench_score_of_fuzzy(gb: &mut Bench) { for pattern in PATTERNS { let task_name = format!("Fuzzy({pattern:?})::score_of"); gb.task(task_name, |b| { let fp = FuzzyPattern::from(pattern); b.iter(|| { for name in shared::NAMES { pretend_used(fp.score_of(name)); } }); }); } } glassbench!( "Fuzzy Patterns", bench_score_of_fuzzy, ); broot-1.46.3/benches/path_normalization.rs000064400000000000000000000012471046102023000167270ustar 00000000000000use { broot::path, glassbench::*, }; static PATHS: &[&str] = &[ "/abc/test/../thing.png", "/abc/def/../../thing.png", "/home/dys/test", "/home/dys", "/home/dys/", "/home/dys/..", "/home/dys/../", "/..", "../test", "/home/dys/../../../test", "/a/b/c/d/e/f/g/h/i/j/k/l/m/n", "/a/b/c/d/e/f/g/h/i/j/k/l/m/n/", "/", "π/2", ]; fn bench_normalization(gb: &mut Bench) { gb.task("normalize_path", |b| { b.iter(|| { for path in PATHS { pretend_used(path::normalize_path(path)); } }); }); } glassbench!( "Path Normalization", bench_normalization, ); broot-1.46.3/benches/shared/mod.rs000064400000000000000000000000361046102023000150450ustar 00000000000000mod names; pub use names::*; broot-1.46.3/benches/shared/names.rs000064400000000000000000000041671046102023000154020ustar 00000000000000pub static NAMES: &[&str] = &[ " brr ooT", "Réveillon", "dys", "test", " tetsesstteststt ", "a rbrroot", "Ab", "test again", "des réveils", "pi", "a quite longer name", "compliqué - 这个大象有多重", "brrooT", "1", "another name.jpeg", "aaaaaab", "a ab abba aab", "abcdrtodota", "palimpsestes désordonnés", "a", "π", "normal.dot", "ùmeé9$njfbaù rz&é", "FactoryFactoryFactoryFactory.java", "leftPad.js", "Cargo.toml", "Cargo.lock", "main.rs", ".gitignore", "lib.rs", " un réveil", "aaaaaaaaaaaaaaaaabbbbbbb", "BABABC B AB", "réveils", "paem", "poëme", "mjrzemrjzm mrjz mrzr rb root", "&cq", "..a", "~~~~~", "ba", "bar", "bar ro ot", "& aé &a é", "mùrz*jfzùenfzeùrjmùe", "krz", "q", "mjrfzm e", "dystroy.org", "www", "termimad", "minimad", "regex", "lazy_regex", "jaquerie", "Tillon", "Tellini", "Garo", "Portequoi", "Terdi", "Ploplo", "le dragon", "l'ours", "la tortue géante", "le chamois", "dystroy", "bra ernre rjrz a e3 broorar/ e/ smallvec/memmap;r b oot4 Z", "un petit peu n'importe quoi", "dans", "cette", "liste", "Broot", " broot", " broot ", "b-root", "biroute", "Miaou", "meow", "et", "surtout", "La Grande Roulette", "this list is", "very obviously", "tailored at stressing", "the engine", "and the reader", "C++", "javascript", "SQL", "C#", "Haskell", "Lisp", "Pascal", "and", "Fortran", "are just missing from this codebase", "denys", "seguret", "is", "the", "author", "bro o o o o o o o o o o o o ot", "bro o o o o o o o o o o o o otz", "br bro boo broot brootz", "b b bb bb ca e 1234 oooot", "Bo br BBBroo OOOOOt", "kir ba lrbvr b rbaz broot", "nrel ora hr rbooo t roo jrzz 7 tz", "not matching anything, is it ?", "ae/r/re /reee/ea", "era", "lrlb rre o", "rjre nr", ]; broot-1.46.3/benches/toks.rs000064400000000000000000000011541046102023000140020ustar 00000000000000mod shared; use { broot::pattern::TokPattern, glassbench::*, }; static PATTERNS: &[&str] = &["a", "réveil", "bro,c", "e,jenc,arec,ehro", "broot"]; fn bench_score_of_toks(gb: &mut Bench) { for pattern in PATTERNS { let task_name = format!("TokPattern({pattern:?})::score_of"); gb.task(task_name, |b| { let fp = TokPattern::new(pattern); b.iter(|| { for name in shared::NAMES { pretend_used(fp.score_of(name)); } }); }); } } glassbench!( "Tokens Patterns", bench_score_of_toks, ); broot-1.46.3/build.rs000064400000000000000000000040771046102023000125210ustar 00000000000000//! This file is executed during broot compilation. //! It builds shell completion scripts and the man page //! //! Note: to see the eprintln messages, run cargo with //! cargo -vv build --release use { clap::CommandFactory, clap_complete::{Generator, Shell}, std::{ env, ffi::OsStr, }, }; include!("src/cli/args.rs"); /// The man page built by clap-mangen is too rough to be used as is. It's only /// used as part of a manual process to update the one in /man/page /// so this generation is usually not needed pub const BUILD_MAN_PAGE: bool = false; fn write_completions_file>(generator: G, out_dir: P) { let mut args = Args::command(); for name in &["broot", "br"] { clap_complete::generate_to( generator, &mut args, (*name).to_string(), &out_dir, ).expect("clap complete generation failed"); } } /// write the shell completion scripts which will be added to /// the release archive fn build_completion_scripts() { let out_dir = env::var_os("OUT_DIR").expect("out dir not set"); write_completions_file(Shell::Bash, &out_dir); write_completions_file(Shell::Elvish, &out_dir); write_completions_file(Shell::Fish, &out_dir); write_completions_file(Shell::PowerShell, &out_dir); write_completions_file(Shell::Zsh, &out_dir); eprintln!("completion scripts generated in {out_dir:?}"); } /// generate the man page from the Clap configuration fn build_man_page() -> std::io::Result<()> { let out_dir = env::var_os("OUT_DIR").expect("out dir not set"); let out_dir = PathBuf::from(out_dir); let cmd = Args::command(); let man = clap_mangen::Man::new(cmd); let mut buffer = Vec::::default(); man.render(&mut buffer)?; let file_path = out_dir.join("broot.1"); std::fs::write(&file_path, buffer)?; eprintln!("map page generated in {file_path:?}"); Ok(()) } fn main() -> std::io::Result<()> { build_completion_scripts(); if BUILD_MAN_PAGE { build_man_page()?; } Ok(()) } broot-1.46.3/features.md000064400000000000000000000027301046102023000132060ustar 00000000000000 This page defines the optional features which may be applied on compilation: * clipboard * trash * kitty-csi-check 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. ## The "clipboard" feature 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. Limits: - the feature doesn't compile right now on some platforms (for example Raspberry) - on some platforms the content leaves the clipboard when you quit broot (so you must paste while broot is still running) ## The "trash" feature This feature enables commands for managing the system Trash. They are `:open_trash`, `:delete_trashed_file`, `:restore_trashed_file`, `:purge_trash`. ## The "kitty-csi-check" feature The Kitty graphics protocol allows displaying images in high resolution in broot. Most terminals don't support it, so support must be verified. 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). 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. broot-1.46.3/man/page000064400000000000000000000110221046102023000124520ustar 00000000000000.\" Manpage for broot .\" Some items starting with a # are replaced on build .TH broot 1 "#date" "#version" "broot manpage" .SH NAME broot \- Tree view, file manager, configurable launcher .SH SYNOPSIS .B broot [\fIflags\fR] [\fIoptions\fR] [path] .br .B br [\fIflags\fR] [\fIoptions\fR] [path] .SH DESCRIPTION \fBbroot\fR lets you explore file hierarchies with a tree-like view, manipulate files, launch actions, and define your own shortcuts. .PP \fBbroot\fR is best launched as \fBbr\fR: this shell function gives you access to more commands, especially \fIcd\fR. The \fBbr\fR shell function is interactively installed on first \fBbroot\fR launch. .PP Flags and options can be classically passed on launch but also written in the configuration file. Each flag has a counter-flag so that you can cancel at command line a flag which has been set in the configuration file. .SH FLAGS FLAGS .TP \fB\-d\fR, \fB\-\-dates\fR Show the last modified date of files and directories .TP \fB\-D\fR, \fB\-\-no\-dates\fR Don\*(Aqt show the last modified date .TP \fB\-f\fR, \fB\-\-only\-folders\fR Only show folders .TP \fB\-F\fR, \fB\-\-no\-only\-folders\fR Show folders and files alike .TP \fB\-\-show\-root\-fs\fR Show filesystem info on top .TP \fB\-g\fR, \fB\-\-show\-git\-info\fR Show git statuses on files and stats on repo .TP \fB\-G\fR, \fB\-\-no\-show\-git\-info\fR Don\*(Aqt show git statuses on files and stats on repo .TP \fB\-\-git\-status\fR Only show files having an interesting git status, including hidden ones .TP \fB\-\-help\fR Print help information .TP \fB\-h\fR, \fB\-\-hidden\fR Show hidden files .TP \fB\-H\fR, \fB\-\-no\-hidden\fR Don\*(Aqt show hidden files .TP \fB\-i\fR, \fB\-\-git\-ignored\fR Show git ignored files .TP \fB\-I\fR, \fB\-\-no\-git\-ignored\fR Don\*(Aqt show git ignored files .TP \fB\-p\fR, \fB\-\-permissions\fR Show permissions .TP \fB\-P\fR, \fB\-\-no\-permissions\fR Don\*(Aqt show permissions .TP \fB\-s\fR, \fB\-\-sizes\fR Show the size of files and directories .TP \fB\-S\fR, \fB\-\-no\-sizes\fR Don\*(Aqt show sizes .TP \fB\-\-sort\-by\-count\fR Sort by count (only show one level of the tree) .TP \fB\-\-sort\-by\-date\fR Sort by date (only show one level of the tree) .TP \fB\-\-sort\-by\-size\fR Sort by size (only show one level of the tree) .TP \fB\-\-sort\-by\-type\fR Same as sort\-by\-type\-dirs\-first .TP \fB\-\-sort\-by\-type\-dirs\-first\fR Sort by type, directories first (only show one level of the tree) .TP \fB\-\-sort\-by\-type\-dirs\-last\fR Sort by type, directories last (only show one level of the tree) .TP \fB\-w\fR, \fB\-\-whale\-spotting\fR Sort by size, show ignored and hidden files .TP \fB\-\-no\-sort\fR Don\*(Aqt sort .TP \fB\-t\fR, \fB\-\-trim\-root\fR Trim the root too and don\*(Aqt show a scrollbar .TP \fB\-T\fR, \fB\-\-no\-trim\-root\fR Don\*(Aqt trim the root level, show a scrollbar .TP \fB\-\-outcmd\fR=\fIOUTCMD\fR Where to write the produced cmd (if any) .TP \fB\-c\fR, \fB\-\-cmd\fR=\fICMD\fR Semicolon separated commands to execute .TP \fB\-\-color\fR=\fICOLOR\fR [default: auto] Whether to have styles and colors (default is usually OK) .br .br [\fIpossible values: \fRauto, yes, no] .TP \fB\-\-conf\fR=\fICONF\fR Semicolon separated paths to specific config files .TP \fB\-\-height\fR=\fIHEIGHT\fR Height (if you don\*(Aqt want to fill the screen or for file export) .TP \fB\-\-install\fR Install or reinstall the br shell function .TP \fB\-\-set\-install\-state\fR=\fISET_INSTALL_STATE\fR Where to write the produced cmd (if any) .br .br [\fIpossible values: \fRundefined, refused, installed] .TP \fB\-\-print\-shell\-function\fR=\fIPRINT_SHELL_FUNCTION\fR Print to stdout the br function for a given shell .TP \fB\-\-listen\fR=\fILISTEN\fR A socket to listen to for commands .TP \fB\-\-get\-root\fR Ask for the current root of the remote broot .TP \fB\-\-write\-default\-conf\fR=\fIWRITE_DEFAULT_CONF\fR Write default conf files in given directory .TP \fB\-\-send\fR=\fISEND\fR A socket that broot sends commands to before quitting .TP \fB\-V\fR, \fB\-\-version\fR Print version .TP .SH BUGS .PP .B broot is known to be slow on most \fIWindows\fR installations. .PP On unix and mac platforms, most problems you may encounter are related to some terminals or terminal multiplexers which either intercepts some standard TTY instructions or break buffering or size querying. The list of shortcuts you can define in the config file is thus dependent of your system. .SH AUTHOR .B broot is free and open-source and is written by \fIdenys.seguret@gmail.com\fR. The source code and documentation are available at https://dystroy.org/broot broot-1.46.3/resources/default-conf/conf.hjson000064400000000000000000000231471046102023000174240ustar 00000000000000############################################################### # This configuration file lets you # - define new commands # - change the shortcut or triggering keys of built-in verbs # - change the colors # - set default values for flags # - set special behaviors on specific paths # - and more... # # Configuration documentation is available at # https://dystroy.org/broot # # This file's format is Hjson ( https://hjson.github.io/ ). Some # properties are commented out. To enable them, remove the `#`. # ############################################################### ############################################################### # Default flags # You can set up flags you want broot to start with by # default, for example `default_flags="-ihp"` if you usually want # to see hidden and gitignored files and the permissions (then # if you don't want the hidden files at a specific launch, # you can launch broot with `br -H`). # A popular flag is the `g` one which displays git related info. # # default_flags: ############################################################### # Terminal's title # If you want the terminal's title to be updated when you change # directory, set a terminal_title pattern by uncommenting one of # the examples below and tuning it to your taste. # # terminal_title: "[broot] {git-name}" # terminal_title: "{file} 🐄" # terminal_title: "-= {file-name} =-" # reset_terminal_title_on_exit: false ############################################################### # Date/Time format # If you want to change the format for date/time, uncomment the # following line and change it according to # https://docs.rs/chrono/0.4.11/chrono/format/strftime/index.html # # date_time_format: %Y/%m/%d %R ############################################################### # uncomment to activate modal mode # # (you really should read https://dystroy.org/broot/modal/ # before as it may not suit everybody even among vim users) # # You may start either in 'command' mode, or in 'input' mode # # modal: true # initial_mode: command ############################################################### # Whether to mark the selected line with a triangle # show_selection_mark: true ############################################################### # Column order # cols_order, if specified, must be a permutation of the following # array. You should keep the name column at the end as it has a # variable length. # # cols_order: [ # mark # git # size # permission # date # count # branch # name # ] ############################################################### # True Colors # If this parameter isn't set, broot tries to automatically # determine whether true colors (24 bits) are available. # As this process is unreliable, you may uncomment this setting # and set it to false or true if you notice the colors in # previewed images are too off. # # true_colors: false ############################################################### # Icons # If you want to display icons in broot, uncomment this line # (see https://dystroy.org/broot/icons for installation and # troubleshooting) # # icon_theme: vscode ############################################################### # Special paths # If some paths must be handled specially, uncomment (and change # this section as per the examples) # Setting "list":"never" on a dir prevents broot from looking at its # children when searching, unless the dir is the selected root. # Setting "sum":"never" on a dir prevents broot from looking at its # children when computing the total size and count of files. # Setting "show":"always" makes a file visible even if its name # starts with a dot. # Setting "list":"always" may be useful on a link to a directory # (they're otherwise not entered by broot unless selected) # special_paths: { "/media" : { list: "never" sum: "never" } "~/.config": { "show": "always" } "trav": { show: always list: "always", sum: "never" } # "~/useless": { "show": "never" } # "~/my-link-I-want-to-explore": { "list": "always" } } ############################################################### # Quit on last cancel # You can usually cancel the last state change on escape. # If you want the escape key to quit broot when there's nothing # to cancel (for example when you just opened broot), uncomment # this parameter # # quit_on_last_cancel: true ############################################################### # Search modes # # broot allows many search modes. # A search mode is defined by # - the way to search: 'fuzzy', 'exact', 'regex', or 'tokens'. # - where to search: file 'name', 'path', or file 'content' # A search pattern may for example be "fuzzy path" (default), # "regex content" or "exact path". # # The search mode is selected from its prefix. For example, if # you type "abc", the default mode is "fuzzy path". If you type # "/abc", the mode is "regex path". If you type "rn/abc", the mode # is "regex name". # # This mapping may be modified. You may want to dedicate the # empty prefix (the one which doesn't need a '/') to the # search mode you use most often. The example below makes it # easy to search on name rather than on the subpath. # # More information on # https://dystroy.org/broot/input/#the-filtering-pattern # # search_modes: { # : fuzzy name # /: regex name # } ############################################################### # File Extension Colors # # uncomment and modify the next section if you want to color # file name depending on their extension # # ext_colors: { # png: rgb(255, 128, 75) # rs: yellow # } ############################################################### # Max file size for content search # # Bigger files are ignored when searching their content. You # can specify this size either in ISO units (eg 5GB) or in # the old binary units (eg 44Kib) content_search_max_file_size: 10MB ############################################################### # Max Panels Count # # Change this if you sometimes want to have more than 2 panels # open # max_panels_count: 2 ############################################################### # Update work dir # # By default, broot process' work dir is kept in sync with the # current's panel root. If you want to keep it unchanged, # uncomment this setting # # update_work_dir: false ############################################################### # Kitty Keyboard extension # # If you want to use advanced keyboard shortcuts in Kitty # compatible terminals (Kitty, Wezterm), set this to true. # # This makes it possible to use shortcuts like 'space-n', # 'ctrl-alt-a-b', 'shift-space', etc. # enable_kitty_keyboard: false ############################################################### # lines around matching line in filtered preview # # When searching the content of a file, you can have either # only the matching lines displayed, or some of the surrounding # ones too. # lines_before_match_in_preview: 1 lines_after_match_in_preview: 1 ############################################################### # transformations before preview # # It's possible to define transformations to apply to some files # before calling one of the default preview renderers in broot. # Below are two examples that you may uncomment and adapt: # preview_transformers: [ // # Use mutool to render any PDF file as an image // # In this example we use placeholders for the input and output files // { // input_extensions: [ "pdf" ] // case doesn't matter // output_extension: png // mode: image // command: [ "mutool", "draw", "-w", "1000", "-o", "{output-path}", "{input-path}" ] // } // # Use LibreOffice to render Office files as images // # In this example, {output-dir} is used to specify where LibreOffice must write the result // { // input_extensions: [ "xls", "xlsx", "doc", "docx", "ppt", "pptx", "ods", "odt", "odp" ] // output_extension: png // mode: image // command: [ // "libreoffice", "--headless", // "--convert-to", "png", // "--outdir", "{output-dir}", // "{input-path}" // ] // } // # Use jq to beautify JSON // # In this example, the command refers to neither the input nor the output, // # so broot pipes them to the stdin and stdout of the jq process // { // input_extensions: [ "json" ] // output_extension: json // mode: text // command: [ "jq" ] // } ] ############################################################### # Imports # # While it's possible to have all configuration in one file, # it's more convenient to split it in several ones. # Importing also allows to set a condition on the terminal's # color, which makes it possible to have a different skin # chosen when your terminal has a light background and when # it has a light one. imports: [ # Verbs are better configured in verbs.hjson. But you # can also add another files for your personal verbs verbs.hjson # This file contains the skin to use when the terminal # is dark (or when this couldn't be determined) { luma: [ dark unknown ] # (un)comment to choose your preferred skin file: skins/dark-blue.hjson //file: skins/catppuccin-macchiato.hjson //file: skins/catppuccin-mocha.hjson //file: skins/dark-gruvbox.hjson //file: skins/dark-orange.hjson //file: skins/solarized-dark.hjson } # This skin is imported when your terminal is light { luma: light # (un)comment to choose your preferred skin //file: skins/solarized-light.hjson file: skins/white.hjson } ] broot-1.46.3/resources/default-conf/skins/catppuccin-macchiato.hjson000064400000000000000000000112371046102023000237020ustar 00000000000000############################################################### # A skin for a terminal with a dark background # This skin uses RGB values so won't work for some # terminals. # # Created by Majixed # Based on the catppuccin-mocha theme by A. Taha Baki # # Doc at https://dystroy.org/broot/skins/ ############################################################### skin: { input: rgb(202, 211, 245) none # fg:none bg:$surface2 selected_line: none rgb(91, 96, 120) # fg:$text bg:none default: rgb(202, 211, 245) none # fg:$overlay0 bg:none tree: rgb(110, 115, 141) none # fg:$sapphire bg:none parent: rgb(125, 196, 228) none file: none none # ### PERMISSIONS # perm__: rgb(184, 192, 224) none # $peach perm_r: rgb(245, 169, 127) none # $maroon perm_w: rgb(238, 153, 160) none # $green perm_x: rgb(166, 218, 149) none # $teal owner: rgb(139, 213, 202) none # $sky group: rgb(145, 215, 227) none # ### DATE # # $subtext1 dates: rgb(184, 192, 224) none # ### DIRECTORY # # $lavender directory: rgb(183, 189, 248) none Bold # $green exe: rgb(166, 218, 149) none # $yellow link: rgb(238, 212, 159) none # $subtext0 pruning: rgb(165, 173, 203) none Italic # ### PREVIEW # # fg:$text bg:$mantle preview_title: rgb(202, 211, 245) rgb(30, 32, 48) # fg:$text bg:$mantle preview: rgb(202, 211, 245) rgb(30, 32, 48) # fg:$overlay0 preview_line_number: rgb(110, 115, 141) none # fg:$overlay0 preview_separator: rgb(110, 115, 141) none # ### MATCH # char_match: rgb(238, 212, 159) rgb(73, 77, 100) Bold Italic content_match: rgb(238, 212, 159) rgb(73, 77, 100) Bold Italic preview_match: rgb(238, 212, 159) rgb(73, 77, 100) Bold Italic # children count # fg:$yellow bg:none count: rgb(238, 212, 159) none sparse: rgb(237, 135, 150) none content_extract: rgb(237, 135, 150) none Italic # ### GIT # git_branch: rgb(245, 169, 127) none git_insertions: rgb(245, 169, 127) none git_deletions: rgb(245, 169, 127) none git_status_current: rgb(245, 169, 127) none git_status_modified: rgb(245, 169, 127) none git_status_new: rgb(245, 169, 127) none Bold git_status_ignored: rgb(245, 169, 127) none git_status_conflicted: rgb(245, 169, 127) none git_status_other: rgb(245, 169, 127) none staging_area_title: rgb(245, 169, 127) none # ### FLAG # flag_label: rgb(237, 135, 150) none flag_value: rgb(237, 135, 150) none Bold # ### STATUS # # fg:none #bg:$mantle status_normal: none rgb(30, 32, 48) # fg:$red bg:$mantle status_italic: rgb(237, 135, 150) rgb(30, 32, 48) Italic # fg:$maroon bg:$mantle status_bold: rgb(238, 153, 160) rgb(30, 32, 48) Bold # fg:$maroon bg:$mantle status_ellipsis: rgb(238, 153, 160) rgb(30, 32, 48) Bold # fg:$text bg:$red status_error: rgb(202, 211, 245) rgb(237, 135, 150) # fg:$maroon bg:$mantle status_job: rgb(238, 153, 160) rgb(40, 38, 37) # fg:$maroon bg:$mantle status_code: rgb(238, 153, 160) rgb(30, 32, 48) Italic # fg:$maroon bg:$mantle mode_command_mark: rgb(238, 153, 160) rgb(30, 32, 48) Bold # ### HELP # # fg:$text help_paragraph: rgb(202, 211, 245) none # fg:$red help_headers: rgb(237, 135, 150) none Bold # fg:$peach help_bold: rgb(245, 169, 127) none Bold # fg:$yellow help_italic: rgb(238, 212, 159) none Italic # fg:green bg:$surface0 help_code: rgb(166, 218, 149) rgb(54, 58, 79) # fg:$overlay0 help_table_border: rgb(110, 115, 141) none # ### HEX # # fg:$text hex_null: rgb(202, 211, 245) none # fg:$peach hex_ascii_graphic: rgb(245, 169, 127) none # fg:$green hex_ascii_whitespace: rgb(166, 218, 149) none # fg: teal hex_ascii_other: rgb(139, 213, 202) none # fg: red hex_non_ascii: rgb(237, 135, 150) none # fg:$text bg:$red file_error: rgb(243, 60, 44) none # ### PURPOSE # purpose_normal: none none purpose_italic: rgb(169, 90, 127) none Italic purpose_bold: rgb(169, 90, 127) none Bold purpose_ellipsis: none none # ### SCROLLBAR # # fg:$surface0 scrollbar_track: rgb(54, 58, 79) none # fg:$surface1 scrollbar_thumb: rgb(91, 96, 120) none # ### GOODTOBAD # good_to_bad_0: rgb(166, 218, 149) none good_to_bad_1: rgb(139, 213, 202) none good_to_bad_2: rgb(145, 215, 227) none good_to_bad_3: rgb(125, 196, 228) none good_to_bad_4: rgb(138, 173, 244) none good_to_bad_5: rgb(183, 189, 248) none good_to_bad_6: rgb(198, 160, 246) none good_to_bad_7: rgb(245, 169, 127) none good_to_bad_8: rgb(238, 153, 160) none good_to_bad_9: rgb(237, 135, 150) none } broot-1.46.3/resources/default-conf/skins/catppuccin-mocha.hjson000064400000000000000000000112221046102023000230330ustar 00000000000000############################################################### # A skin for a terminal with a dark background # This skin uses RGB values so won't work for some # terminals. # # Created by A. Taha Baki # Based on the built-in gruvbox theme. # # Doc at https://dystroy.org/broot/skins/ ############################################################### skin: { input: rgb(205, 214, 244) none # fg:none bg:$surface2 selected_line: none rgb(88, 91, 112) # fg:$text bg:none default: rgb(205, 214, 244) none # fg:$overlay0 bg:none tree: rgb(108, 112, 134) none # fg:$sapphire bg:none parent: rgb(116, 199, 236) none file: none none # ### PERMISSIONS # perm__: rgb(186, 194, 222) none # $peach perm_r: rgb(250, 179, 135) none # $maroon perm_w: rgb(235, 160, 172) none # $green perm_x: rgb(166, 227, 161) none # $teal owner: rgb(148, 226, 213) none # $sky group: rgb(137, 220, 235) none # ### DATE # # $subtext1 dates: rgb(186, 194, 222) none # ### DIRECTORY # # $lavender directory: rgb(180, 190, 254) none Bold # $green exe: rgb(166, 227, 161) none # $yellow link: rgb(249, 226, 175) none # $subtext0 pruning: rgb(166, 173, 200) none Italic # ### PREVIEW # # fg:$text bg:$mantle preview_title: rgb(205, 214, 244) rgb(24, 24, 37) # fg:$text bg:$mantle preview: rgb(205, 214, 244) rgb(24, 24, 37) # fg:$overlay0 preview_line_number: rgb(108, 112, 134) none # fg:$overlay0 preview_separator: rgb(108, 112, 134) none # ### MATCH # char_match: rgb(249, 226, 175) rgb(69, 71, 90) Bold Italic content_match: rgb(249, 226, 175) rgb(69, 71, 90) Bold Italic preview_match: rgb(249, 226, 175) rgb(69, 71, 90) Bold Italic # children count # fg:$yellow bg:none count: rgb(249, 226, 175) none sparse: rgb(243, 139, 168) none content_extract: rgb(243, 139, 168) none Italic # ### GIT # git_branch: rgb(250, 179, 135) none git_insertions: rgb(250, 179, 135) none git_deletions: rgb(250, 179, 135) none git_status_current: rgb(250, 179, 135) none git_status_modified: rgb(250, 179, 135) none git_status_new: rgb(250, 179, 135) none Bold git_status_ignored: rgb(250, 179, 135) none git_status_conflicted: rgb(250, 179, 135) none git_status_other: rgb(250, 179, 135) none staging_area_title: rgb(250, 179, 135) none # ### FLAG # flag_label: rgb(243, 139, 168) none flag_value: rgb(243, 139, 168) none Bold # ### STATUS # # fg:none #bg:$mantle status_normal: none rgb(24, 24, 37) # fg:$red bg:$mantle status_italic: rgb(243, 139, 168) rgb(24, 24, 37) Italic # fg:$maroon bg:$mantle status_bold: rgb(235, 160, 172) rgb(24, 24, 37) Bold # fg:$maroon bg:$mantle status_ellipsis: rgb(235, 160, 172) rgb(24, 24, 37) Bold # fg:$text bg:$red status_error: rgb(205, 214, 244) rgb(243, 139, 168) # fg:$maroon bg:$mantle status_job: rgb(235, 160, 172) rgb(40, 38, 37) # fg:$maroon bg:$mantle status_code: rgb(235, 160, 172) rgb(24, 24, 37) Italic # fg:$maroon bg:$mantle mode_command_mark: rgb(235, 160, 172) rgb(24, 24, 37) Bold # ### HELP # # fg:$text help_paragraph: rgb(205, 214, 244) none # fg:$red help_headers: rgb(243, 139, 168) none Bold # fg:$peach help_bold: rgb(250, 179, 135) none Bold # fg:$yellow help_italic: rgb(249, 226, 175) none Italic # fg:green bg:$surface0 help_code: rgb(166, 227, 161) rgb(49, 50, 68) # fg:$overlay0 help_table_border: rgb(108, 112, 134) none # ### HEX # # fg:$text hex_null: rgb(205, 214, 244) none # fg:$peach hex_ascii_graphic: rgb(250, 179, 135) none # fg:$green hex_ascii_whitespace: rgb(166, 227, 161) none # fg: teal hex_ascii_other: rgb(148, 226, 213) none # fg: red hex_non_ascii: rgb(243, 139, 168) none # fg:$text bg:$red file_error: rgb(251, 73, 52) none # ### PURPOSE # purpose_normal: none none purpose_italic: rgb(177, 98, 134) none Italic purpose_bold: rgb(177, 98, 134) none Bold purpose_ellipsis: none none # ### SCROLLBAR # # fg:$surface0 scrollbar_track: rgb(49, 50, 68) none # fg:$surface1 scrollbar_thumb: rgb(88, 91, 112) none # ### GOODTOBAD # good_to_bad_0: rgb(166, 227, 161) none good_to_bad_1: rgb(148, 226, 213) none good_to_bad_2: rgb(137, 220, 235) none good_to_bad_3: rgb(116, 199, 236) none good_to_bad_4: rgb(137, 180, 250) none good_to_bad_5: rgb(180, 190, 254) none good_to_bad_6: rgb(203, 166, 247) none good_to_bad_7: rgb(250, 179, 135) none good_to_bad_8: rgb(235, 160, 172) none good_to_bad_9: rgb(243, 139, 168) none } broot-1.46.3/resources/default-conf/skins/dark-blue.hjson000064400000000000000000000100431046102023000214630ustar 00000000000000############################################################### # A skin for a terminal with a dark background # # To create your own skin, copy this file, change the entries # and import your skin file from the main conf file (look # for "imports") # # Doc at https://dystroy.org/broot/skins/ ############################################################### ############################################################### # Skin # If you want to change the colors of broot, # uncomment the following block and start messing # with the various values. # A skin entry value is made of two parts separated with a '/': # The first one is the skin for the active panel. # The second one, optional, is the skin for non active panels. # You may find explanations and other skins on # https://dystroy.org/broot/skins ############################################################### skin: { default: gray(22) none / gray(20) none tree: gray(8) None / gray(4) None parent: gray(18) None / gray(13) None file: gray(22) None / gray(15) None directory: ansi(110) None bold / ansi(110) None exe: Cyan None link: Magenta None pruning: gray(12) None Italic perm__: gray(5) None perm_r: ansi(94) None perm_w: ansi(132) None perm_x: ansi(65) None owner: ansi(138) None group: ansi(131) None count: ansi(138) gray(4) dates: ansi(66) None sparse: ansi(214) None content_extract: ansi(29) None content_match: ansi(34) None device_id_major: ansi(138) None device_id_sep: ansi(102) None device_id_minor: ansi(138) None git_branch: ansi(178) None git_insertions: ansi(28) None git_deletions: ansi(160) None git_status_current: gray(5) None git_status_modified: ansi(28) None git_status_new: ansi(94) None bold git_status_ignored: gray(17) None git_status_conflicted: ansi(88) None git_status_other: ansi(88) None selected_line: None gray(6) / None gray(4) char_match: Green None file_error: Red None flag_label: gray(15) gray(2) flag_value: ansi(178) gray(2) bold input: White gray(2) / gray(15) None status_error: gray(22) ansi(124) status_job: ansi(220) gray(5) status_normal: gray(20) gray(4) / gray(2) gray(2) status_italic: ansi(178) gray(4) / gray(2) gray(2) status_bold: ansi(178) gray(4) bold / gray(2) gray(2) status_code: ansi(229) gray(4) / gray(2) gray(2) status_ellipsis: gray(19) gray(1) / gray(2) gray(2) purpose_normal: gray(20) gray(2) purpose_italic: ansi(178) gray(2) purpose_bold: ansi(178) gray(2) bold purpose_ellipsis: gray(20) gray(2) scrollbar_track: gray(7) None / gray(4) None scrollbar_thumb: gray(22) None / gray(14) None help_paragraph: gray(20) None help_bold: ansi(178) None bold help_italic: ansi(229) None help_code: gray(21) gray(3) help_headers: ansi(178) None help_table_border: ansi(239) None preview: gray(20) gray(1) / gray(18) gray(2) preview_title: gray(23) gray(2) / gray(21) gray(2) preview_line_number: gray(12) gray(3) preview_separator: gray(5) None preview_match: None ansi(29) hex_null: gray(8) None hex_ascii_graphic: gray(18) None hex_ascii_whitespace: ansi(143) None hex_ascii_other: ansi(215) None hex_non_ascii: ansi(167) None staging_area_title: gray(22) gray(2) / gray(20) gray(3) mode_command_mark: gray(5) ansi(204) bold good_to_bad_0: ansi(28) good_to_bad_1: ansi(29) good_to_bad_2: ansi(29) good_to_bad_3: ansi(29) good_to_bad_4: ansi(29) good_to_bad_5: ansi(100) good_to_bad_6: ansi(136) good_to_bad_7: ansi(172) good_to_bad_8: ansi(166) good_to_bad_9: ansi(196) } ############################################################### # Syntax Theme # # If you want to choose the theme used for preview, uncomment # one of the following lines: # # syntax_theme: GitHub # syntax_theme: SolarizedDark # syntax_theme: SolarizedLight syntax_theme: MochaDark # syntax_theme: OceanDark # syntax_theme: OceanLight broot-1.46.3/resources/default-conf/skins/dark-gruvbox.hjson000064400000000000000000000073731046102023000222440ustar 00000000000000############################################################### # A skin for a terminal with a dark background # This skin uses RGB values so won't work for some # terminals. # # # (initially contributed by @basbebe) # # Doc at https://dystroy.org/broot/skins/ ############################################################### skin: { default: rgb(235, 219, 178) none / rgb(189, 174, 147) none tree: rgb(70, 70, 80) None / rgb(60, 60, 60) None parent: rgb(235, 219, 178) none / rgb(189, 174, 147) none Italic file: None None / None None Italic directory: rgb(131, 165, 152) None Bold / rgb(131, 165, 152) None exe: rgb(184, 187, 38) None link: rgb(104, 157, 106) None pruning: rgb(124, 111, 100) None Italic perm__: None None perm_r: rgb(215, 153, 33) None perm_w: rgb(204, 36, 29) None perm_x: rgb(152, 151, 26) None owner: rgb(215, 153, 33) None Bold group: rgb(215, 153, 33) None count: rgb(69, 133, 136) rgb(50, 48, 47) dates: rgb(168, 153, 132) None sparse: rgb(250, 189,47) None content_extract: ansi(29) None Italic content_match: ansi(34) None Bold git_branch: rgb(251, 241, 199) None git_insertions: rgb(152, 151, 26) None git_deletions: rgb(190, 15, 23) None git_status_current: rgb(60, 56, 54) None git_status_modified: rgb(152, 151, 26) None git_status_new: rgb(104, 187, 38) None Bold git_status_ignored: rgb(213, 196, 161) None git_status_conflicted: rgb(204, 36, 29) None git_status_other: rgb(204, 36, 29) None selected_line: None rgb(60, 56, 54) / None rgb(50, 48, 47) char_match: rgb(250, 189, 47) None file_error: rgb(251, 73, 52) None flag_label: rgb(189, 174, 147) None flag_value: rgb(211, 134, 155) None Bold input: rgb(251, 241, 199) None / rgb(189, 174, 147) None Italic status_error: rgb(213, 196, 161) rgb(204, 36, 29) status_job: rgb(250, 189, 47) rgb(60, 56, 54) status_normal: None rgb(40, 38, 37) / None None status_italic: rgb(211, 134, 155) rgb(40, 38, 37) Italic / None None status_bold: rgb(211, 134, 155) rgb(40, 38, 37) Bold / None None status_code: rgb(251, 241, 199) rgb(40, 38, 37) / None None status_ellipsis: rgb(251, 241, 199) rgb(40, 38, 37) Bold / None None purpose_normal: None None purpose_italic: rgb(177, 98, 134) None Italic purpose_bold: rgb(177, 98, 134) None Bold purpose_ellipsis: None None scrollbar_track: rgb(80, 73, 69) None / rgb(50, 48, 47) None scrollbar_thumb: rgb(213, 196, 161) None / rgb(102, 92, 84) None help_paragraph: None None help_bold: rgb(214, 93, 14) None Bold help_italic: rgb(211, 134, 155) None Italic help_code: rgb(142, 192, 124) rgb(50, 48, 47) help_headers: rgb(254, 128, 25) None Bold help_table_border: rgb(80, 73, 69) None preview_title: rgb(235, 219, 178) rgb(40, 40, 40) / rgb(189, 174, 147) rgb(40, 40, 40) preview: rgb(235, 219, 178) rgb(40, 40, 40) / rgb(235, 219, 178) rgb(40, 40, 40) preview_line_number: rgb(124, 111, 100) None / rgb(124, 111, 100) rgb(40, 40, 40) preview_separator: rgb(70, 70, 80) None / rgb(60, 60, 60) None preview_match: None ansi(29) Bold hex_null: rgb(189, 174, 147) None hex_ascii_graphic: rgb(213, 196, 161) None hex_ascii_whitespace: rgb(152, 151, 26) None hex_ascii_other: rgb(254, 128, 25) None hex_non_ascii: rgb(214, 93, 14) None staging_area_title: rgb(235, 219, 178) rgb(40, 40, 40) / rgb(189, 174, 147) rgb(40, 40, 40) mode_command_mark: gray(5) ansi(204) Bold good_to_bad_0: ansi(28) good_to_bad_1: ansi(29) good_to_bad_2: ansi(29) good_to_bad_3: ansi(29) good_to_bad_4: ansi(29) good_to_bad_5: ansi(100) good_to_bad_6: ansi(136) good_to_bad_7: ansi(172) good_to_bad_8: ansi(166) good_to_bad_9: ansi(196) } broot-1.46.3/resources/default-conf/skins/dark-orange.hjson000064400000000000000000000073321046102023000220160ustar 00000000000000############################################################### # A skin for a terminal with a dark background # # To create your own skin, copy this file, change the entries # and import your skin file from the main conf file (look # for "imports") # # Doc at https://dystroy.org/broot/skins/ ############################################################### ############################################################### # Skin # If you want to change the colors of broot, # uncomment the following bloc and start messing # with the various values. # A skin entry value is made of two parts separated with a '/': # The first one is the skin for the active panel. # The second one, optional, is the skin for non active panels. # You may find explanations and other skins on # https://dystroy.org/broot/skins ############################################################### skin: { default: none none / gray(20) none tree: ansi(94) None / gray(3) None parent: gray(18) None / gray(13) None file: gray(20) None / gray(15) None directory: ansi(208) None Bold / ansi(172) None bold exe: Cyan None link: Magenta None pruning: gray(12) None Italic perm__: gray(5) None perm_r: ansi(94) None perm_w: ansi(132) None perm_x: ansi(65) None owner: ansi(138) None group: ansi(131) None count: ansi(136) gray(3) dates: ansi(66) None sparse: ansi(214) None content_extract: ansi(29) None content_match: ansi(34) None git_branch: ansi(229) None git_insertions: ansi(28) None git_deletions: ansi(160) None git_status_current: gray(5) None git_status_modified: ansi(28) None git_status_new: ansi(94) None Bold git_status_ignored: gray(17) None git_status_conflicted: ansi(88) None git_status_other: ansi(88) None selected_line: None gray(5) / None gray(4) char_match: Yellow None file_error: Red None flag_label: gray(15) None flag_value: ansi(208) None Bold input: White None / gray(15) gray(2) status_error: gray(22) ansi(124) status_job: ansi(220) gray(5) status_normal: gray(20) gray(3) / gray(2) gray(2) status_italic: ansi(208) gray(3) / gray(2) gray(2) status_bold: ansi(208) gray(3) Bold / gray(2) gray(2) status_code: ansi(229) gray(3) / gray(2) gray(2) status_ellipsis: gray(19) gray(1) / gray(2) gray(2) purpose_normal: gray(20) gray(2) purpose_italic: ansi(178) gray(2) purpose_bold: ansi(178) gray(2) Bold purpose_ellipsis: gray(20) gray(2) scrollbar_track: gray(7) None / gray(4) None scrollbar_thumb: gray(22) None / gray(14) None help_paragraph: gray(20) None help_bold: ansi(208) None Bold help_italic: ansi(166) None help_code: gray(21) gray(3) help_headers: ansi(208) None help_table_border: ansi(239) None preview: gray(20) gray(1) / gray(18) gray(2) preview_line_number: gray(12) gray(3) preview_separator: ansi(94) None / gray(3) None preview_match: None ansi(29) hex_null: gray(11) None hex_ascii_graphic: gray(18) None hex_ascii_whitespace: ansi(143) None hex_ascii_other: ansi(215) None hex_non_ascii: ansi(167) None good_to_bad_0: ansi(28) good_to_bad_1: ansi(29) good_to_bad_2: ansi(29) good_to_bad_3: ansi(29) good_to_bad_4: ansi(29) good_to_bad_5: ansi(100) good_to_bad_6: ansi(136) good_to_bad_7: ansi(172) good_to_bad_8: ansi(166) good_to_bad_9: ansi(196) } ############################################################### # Syntax Theme # # If you want to choose the theme used for preview, uncomment # one of the following lines: # # syntax_theme: GitHub syntax_theme: SolarizedDark # syntax_theme: SolarizedLight # syntax_theme: MochaDark # syntax_theme: OceanDark # syntax_theme: OceanLight broot-1.46.3/resources/default-conf/skins/native-16.hjson000064400000000000000000000061121046102023000213310ustar 00000000000000############################################################### # 16 ANSI color theme. Colors in this theme are restricted from # ANSI color 0 - 15. This will allow the theme to adapt to your # terminal emulator's theme. Note that, for now, the preview # mode does not yet support this theme because of syntect not # having a 16 ansi color theme. # # More info at https://jeffkreeftmeijer.com/vim-16-color/ # Doc at https://dystroy.org/broot/skins/ ############################################################### skin: { directory: ansi(12) file: ansi(7) pruning: ansi(8) none italic selected_line: none ansi(0) tree: ansi(8) # Search char_match: ansi(3) none underlined parent: ansi(4) none bold # File properties exe: ansi(2) link: ansi(13) sparse: ansi(12) # Prompt input: ansi(6) # Status bar status_bold: ansi(7) ansi(8) bold status_code: ansi(10) ansi(8) status_ellipsis: ansi(7) ansi(8) status_error: ansi(7) ansi(8) status_italic: ansi(7) ansi(8) italic status_job: ansi(7) ansi(8) status_normal: ansi(7) ansi(8) # Flag status flag_label: ansi(6) flag_value: ansi(14) none bold # Background default: none none # Scrollbar scrollbar_track: ansi(0) scrollbar_thumb: ansi(3) # Git git_branch: ansi(13) git_deletions: ansi(1) git_insertions: ansi(2) git_status_conflicted: ansi(1) git_status_current: ansi(6) git_status_ignored: ansi(8) git_status_modified: ansi(3) git_status_new: ansi(2) none bold git_status_other: ansi(5) # Staging area staging_area_title: ansi(3) # Documentation help_bold: ansi(7) none bold help_code: ansi(4) help_headers: ansi(3) help_italic: ansi(7) none italic help_paragraph: ansi(7) help_table_border: ansi(8) # Device column device_id_major: ansi(5) device_id_minor: ansi(5) device_id_sep: ansi(5) # Counts column count: ansi(13) # Dates column dates: ansi(6) # Permissions column group: ansi(3) owner: ansi(3) perm__: ansi(8) perm_r: ansi(3) perm_w: ansi(1) perm_x: ansi(2) # Hex preview hex_null: ansi(8) hex_ascii_graphic: ansi(2) hex_ascii_whitespace: ansi(3) hex_ascii_other: ansi(4) hex_non_ascii: ansi(5) # Preview # preview: none # preview_line_number: none # preview_match: none # preview_title: none # Used for displaying errors file_error: ansi(1) # Content searches content_extract: ansi(7) content_match: ansi(3) none underlined # Used in status line purpose_bold: ansi(0) ansi(7) bold purpose_ellipsis: ansi(0) purpose_italic: ansi(0) ansi(7) italic purpose_normal: ansi(0) # Modal indicator mode_command_mark: ansi(7) ansi(4) # File system occupation good_to_bad_0: ansi(2) good_to_bad_1: ansi(2) good_to_bad_2: ansi(2) good_to_bad_3: ansi(2) good_to_bad_4: ansi(2) good_to_bad_5: ansi(1) good_to_bad_6: ansi(1) good_to_bad_7: ansi(1) good_to_bad_8: ansi(1) good_to_bad_9: ansi(1) } broot-1.46.3/resources/default-conf/skins/solarized-dark.hjson000064400000000000000000000121061046102023000225320ustar 00000000000000// contributed by [@danieltrautmann](https://github.com/danieltrautmann) // // // The Solarized Dark skin uses RGB values, so it might not work well with some // terminals // // Doc at https://dystroy.org/broot/skins/ skin: { default: "rgb(131, 148, 150) rgb(0, 43, 54) / rgb(131, 148, 150) rgb(7, 54, 66)" // base0 base03 / base01 base02 tree: "rgb(88, 110, 117) none" // base01 default file: "none none" // default default directory: "rgb(38, 139, 210) none bold" // blue default bold exe: "rgb(211, 1, 2) none" // red default link: "rgb(211, 54, 130) none" // magenta default pruning: "rgb(88, 110, 117) none italic" // base01 default italic perm__: "rgb(88, 110, 117) none" // base01 default perm_r: "none none" // default default perm_w: "none none" // default default perm_x: "none none" // default default owner: "rgb(88, 110, 117) none" // base01 default group: "rgb(88, 110, 117) none" // base01 default sparse: "none none" // default default git_branch: "rgb(147, 161, 161) none" // base1 default git_insertions: "rgb(133, 153, 0) none" // green default git_deletions: "rgb(211, 1, 2) none" // red default git_status_current: "none none" // default default git_status_modified: "rgb(181, 137, 0) none" // yellow default git_status_new: "rgb(133, 153, 0) none" // green default git_status_ignored: "rgb(88, 110, 117) none" // base01 default git_status_conflicted: "rgb(211, 1, 2) none" // red default git_status_other: "rgb(211, 1, 2) none" // red default selected_line: "none rgb(7, 54, 66)" // default base02 char_match: "rgb(133, 153, 0) none underlined" // green default underlined file_error: "rgb(203, 75, 22) none italic" // orange default italic flag_label: "none none" // default default flag_value: "rgb(181, 137, 0) none bold" // yellow default bold input: "none none" // default default status_error: "rgb(203, 75, 22) rgb(7, 54, 66)" // orange base02 status_job: "rgb(108, 113, 196) rgb(7, 54, 66) bold" // violet base02 bold status_normal: "none rgb(7, 54, 66)" // default base02 status_italic: "rgb(181, 137, 0) rgb(7, 54, 66)" // yellow base02 status_bold: "rgb(147, 161, 161) rgb(7, 54, 66) bold" // base1 base02 bold status_code: "rgb(108, 113, 196) rgb(7, 54, 66)" // violet base02 status_ellipsis: "none rgb(7, 54, 66)" // default base02 scrollbar_track: "rgb(7, 54, 66) none" // base02 default scrollbar_thumb: "none none" // default default help_paragraph: "none none" // default default help_bold: "rgb(147, 161, 161) none bold" // base1 default bold help_italic: "rgb(147, 161, 161) none italic" // base1 default italic help_code: "rgb(147, 161, 161) rgb(7, 54, 66)" // base1 base02 help_headers: "rgb(181, 137, 0) none" // yellow default help_table_border: "none none" // default default preview_title: "gray(20) rgb(0, 43, 54)" staging_area_title: "gray(22) rgb(0, 43, 54)" good_to_bad_0: "ansi(28)" // green good_to_bad_1: "ansi(29)" good_to_bad_2: "ansi(29)" good_to_bad_3: "ansi(29)" good_to_bad_4: "ansi(29)" good_to_bad_5: "ansi(100)" good_to_bad_6: "ansi(136)" good_to_bad_7: "ansi(172)" good_to_bad_8: "ansi(166)" good_to_bad_9: "ansi(196)" // red } broot-1.46.3/resources/default-conf/skins/solarized-light.hjson000064400000000000000000000070151046102023000227230ustar 00000000000000// contributed by [@danieltrautmann](https://github.com/danieltrautmann) // // // The Solarized Light skin uses RGB values, so it might not work well with some // terminals // // If you prefer to keep the background the same as your terminal background, change // the "default" entry to // default: "none none / rgb(147, 161, 161) none" // // Doc at https://dystroy.org/broot/skins/ skin: { // base00 default / base1 base2 default: "rgb(101, 123, 131) none / rgb(147, 161, 161) none" // base1 default tree: "rgb(147, 161, 161) none" // default default file: "none none" // blue default bold directory: "rgb(38, 139, 210) none bold" // red default exe: "rgb(211, 1, 2) none" // magenta default link: "rgb(211, 54, 130) none" // base1 default italic pruning: "rgb(147, 161, 161) none italic" // base1 default perm__: "rgb(147, 161, 161) none" // default default perm_r: "none none" // default default perm_w: "none none" // default default perm_x: "none none" // base1 default owner: "rgb(147, 161, 161) none" // base1 default group: "rgb(147, 161, 161) none" // default default sparse: "none none" // base01 default git_branch: "rgb(88, 110, 117) none" // green default git_insertions: "rgb(133, 153, 0) none" // red default git_deletions: "rgb(211, 1, 2) none" // default default git_status_current: "none none" // yellow default git_status_modified: "rgb(181, 137, 0) none" // green default git_status_new: "rgb(133, 153, 0) none" // base1 default git_status_ignored: "rgb(147, 161, 161) none" // red default git_status_conflicted: "rgb(211, 1, 2) none" // red default git_status_other: "rgb(211, 1, 2) none" // default base2 selected_line: "none rgb(238, 232, 213)" // green default underlined char_match: "rgb(133, 153, 0) none underlined" // orange default italic file_error: "rgb(203, 75, 22) none italic" // default default flag_label: "none none" // yellow default bold flag_value: "rgb(181, 137, 0) none bold" // default default input: "none none" // orange base2 status_error: "rgb(203, 75, 22) rgb(238, 232, 213)" // violet base2 bold status_job: "rgb(108, 113, 196) rgb(238, 232, 213) bold" // default base2 status_normal: "none rgb(238, 232, 213)" // yellow base2 status_italic: "rgb(181, 137, 0) rgb(238, 232, 213)" // base01 base2 bold status_bold: "rgb(88, 110, 117) rgb(238, 232, 213) bold" // violet base2 status_code: "rgb(108, 113, 196) rgb(238, 232, 213)" // default base2 status_ellipsis: "none rgb(238, 232, 213)" // base2 default scrollbar_track: "rgb(238, 232, 213) none" // default default scrollbar_thumb: "none none" // default default help_paragraph: "none none" // base01 default bold help_bold: "rgb(88, 110, 117) none bold" // base01 default italic help_italic: "rgb(88, 110, 117) none italic" // base01 base2 help_code: "rgb(88, 110, 117) rgb(238, 232, 213)" // yellow default help_headers: "rgb(181, 137, 0) none" // default default help_table_border: "none none" preview_title: "rgb(147, 161, 161) rgb(238, 232, 213)" preview: "rgb(101, 123, 131) rgb(253, 246, 227) / rgb(147, 161, 161) rgb(238, 232, 213)" preview_line_number: "rgb(147, 161, 161) rgb(238, 232, 213)" preview_separator: "rgb(147, 161, 161) rgb(238, 232, 213)" preview_match: "None ansi(29)" staging_area_title: "gray(22) rgb(253, 246, 227)" good_to_bad_0: ansi(28) good_to_bad_1: ansi(29) good_to_bad_2: ansi(29) good_to_bad_3: ansi(29) good_to_bad_4: ansi(29) good_to_bad_5: ansi(100) good_to_bad_6: ansi(136) good_to_bad_7: ansi(172) good_to_bad_8: ansi(166) good_to_bad_9: ansi(196) } broot-1.46.3/resources/default-conf/skins/white.hjson000064400000000000000000000051421046102023000207410ustar 00000000000000############################################################### # A skin for a terminal with a white background # # To create your own skin, copy this file, change the entries # and import your skin file from the main conf file (look # for "imports") # # Doc at https://dystroy.org/broot/skins/ ############################################################### syntax_theme: base16-ocean.light skin: { default: gray(1) None tree: gray(7) None / gray(18) None file: gray(3) None / gray(8) None directory: ansi(25) None Bold / ansi(25) None exe: ansi(130) None link: Magenta None pruning: gray(12) None Italic perm__: gray(5) None perm_r: ansi(94) None perm_w: ansi(132) None perm_x: ansi(65) None owner: ansi(138) None group: ansi(131) None dates: ansi(66) None sparse: ansi(214) None git_branch: ansi(229) None git_insertions: ansi(28) None git_deletions: ansi(160) None git_status_current: gray(5) None git_status_modified: ansi(28) None git_status_new: ansi(94) None Bold git_status_ignored: gray(17) None git_status_conflicted: ansi(88) None git_status_other: ansi(88) None selected_line: None gray(19) / None gray(21) char_match: ansi(22) None file_error: Red None flag_label: gray(9) None flag_value: ansi(166) None Bold input: gray(1) None / gray(4) gray(20) status_error: gray(22) ansi(124) status_normal: gray(2) gray(20) status_job: ansi(220) gray(5) status_italic: ansi(166) gray(20) status_bold: ansi(166) gray(20) status_code: ansi(17) gray(20) status_ellipsis: gray(19) gray(15) purpose_normal: gray(20) gray(2) purpose_italic: ansi(178) gray(2) purpose_bold: ansi(178) gray(2) Bold purpose_ellipsis: gray(20) gray(2) scrollbar_track: gray(20) none scrollbar_thumb: ansi(238) none help_paragraph: gray(2) none help_bold: ansi(202) none bold help_italic: ansi(202) none italic help_code: gray(5) gray(22) help_headers: ansi(202) none help_table_border: ansi(239) None preview_title: gray(3) None / gray(5) None preview: gray(5) gray(23) / gray(7) gray(23) preview_line_number: gray(6) gray(20) preview_separator: gray(7) None / gray(18) None preview_match: None ansi(29) Underlined hex_null: gray(15) None hex_ascii_graphic: gray(2) None hex_ascii_whitespace: ansi(143) None hex_ascii_other: ansi(215) None hex_non_ascii: ansi(167) None staging_area_title: gray(8) None / gray(13) None mode_command_mark: gray(15) ansi(204) Bold good_to_bad_0: ansi(28) good_to_bad_1: ansi(29) good_to_bad_2: ansi(29) good_to_bad_3: ansi(29) good_to_bad_4: ansi(29) good_to_bad_5: ansi(100) good_to_bad_6: ansi(136) good_to_bad_7: ansi(172) good_to_bad_8: ansi(166) good_to_bad_9: ansi(196) } broot-1.46.3/resources/default-conf/verbs.hjson000064400000000000000000000112741046102023000176160ustar 00000000000000############################################################### # This file contains the verb definitions for broot # # Some verbs here are examples and not enabled by default: you # need to uncomment them if you want to use them. # # Documentation at https://dystroy.org/broot/verbs/ ############################################################### verbs: [ # You should customize this standard opening of text files. # If you edit text files in your terminal (vi, emacs, helix, eg.), then # you'll find it convenient to change the 'key' from 'ctrl-e' to 'enter'. # # If $EDITOR isn't set on your computer, you should either set it using # something similar to # export EDITOR=/usr/local/bin/nvim # or just replace it with your editor of choice in the 'execution' # pattern. # If your editor is able to open a file on a specific line, use {line} # so that you may jump directly at the right line from a preview or # a content search. # Examples depending on your favourite editor: # execution: "nvim +{line} {file}" # execution: "helix {file}:{line}" { invocation: edit shortcut: e key: ctrl-e apply_to: text_file execution: "$EDITOR {file}" leave_broot: false } # Example 1: launching `tail -n` on the selected file (leaving broot) # { # name: tail_lines # invocation: tl {lines_count} # execution: "tail -f -n {lines_count} {file}" # } # Example 2: creating a new file without leaving broot # { # name: touch # invocation: touch {new_file} # execution: "touch {directory}/{new_file}" # leave_broot: false # } # A convenient shortcut to create new text files in # the current directory or below { invocation: create {subpath} execution: "$EDITOR {directory}/{subpath}" leave_broot: false } { invocation: git_diff shortcut: gd leave_broot: false execution: "git difftool -y {file}" } # On ctrl-b, propose the creation of a copy of the selection. # While this might occasionally be useful, this verb is mostly here # as an example to demonstrate rare standard groups like {file-stem} # and {file-dot-extension} and the auto_exec verb property which # allows verbs to stay unexecuted until you hit enter { invocation: "backup {version}" key: ctrl-b leave_broot: false auto_exec: false execution: "cp -r {file} {parent}/{file-stem}-{version}{file-dot-extension}" } # By default, `rm` does the system rm, and completely removes # the file. If you prefer to have the file moved to the system # trash, you may use the ':trash' internal with the verb below: # { # invocation: "rm" # internal: "trash" # leave_broot: false # } # This verb lets you launch a terminal on ctrl-T # (on exit you'll be back in broot) { invocation: terminal key: ctrl-t execution: "$SHELL" set_working_dir: true leave_broot: false } # Here's an example of a verb needing the shell capabilities. # It copies all children of the currently selected directory # to a destination you type. # It uses a star, which needs the shell for expansion. That's # why such verb must have the `from_shell: true` parameter. # { # invocation: "cpa {dest}" # external: "cp -r {directory}/* {dest}" # from_shell: true # } # Here's an example of a shortcut bringing you to your home directory # { # invocation: home # key: ctrl-home # execution: ":focus ~" # } # Here's going to the work-dir root of the current git repository # { # invocation: gtr # execution: ":focus {git-root}" # } # A popular set of shortcuts for going up and down: # # { # key: ctrl-k # execution: ":line_up" # } # { # key: ctrl-j # execution: ":line_down" # } # { # key: ctrl-u # execution: ":page_up" # } # { # key: ctrl-d # execution: ":page_down" # } # If you develop using git, you might like to often switch # to the git status filter: # { # key: alt-g # execution: ":toggle_git_status" # } # You can reproduce the bindings of Norton Commander # on copying or moving to the other panel: # { # key: F5 # external: "cp -r {file} {other-panel-directory}" # leave_broot: false # } # { # key: F6 # external: "mv {file} {other-panel-directory}" # leave_broot: false # } ] broot-1.46.3/resources/icons/nerdfont/README.md000064400000000000000000000025001046102023000172640ustar 00000000000000# Nerd Fonts Icons ## Requirements [Nerd Fonts](https://github.com/ryanoasis/nerd-fonts) installed either through a patched font or available as a fallback font. ## Configuration In broot config file, set ``` icon_theme: nerdfont ``` ## Limitations 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. ## Editing the Icon for a File: If you want to find an icon for a file: go to https://www.nerdfonts.com/cheat-sheet and search for: - 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 ), //  - a icon code like "0xf15b" without the "0x" prefix. This should return the corresponding "" icon. ## Tips on editing these files in vi 1. Open ./icon_name_to_icon_code_point_map.rs then in the same session, switch to file you want to edit use C-n and C-y in edit mode 2. This plugin currently searches for lowercase, make everything so 3. Remember to run :Tabularize over ')' and ','. The tabular Plugin 4. :'<,'>!sort 5. `cargo run` in debug mode should figure out some problems. broot-1.46.3/resources/icons/nerdfont/data/double_extension_to_icon_name_map.rs000064400000000000000000000074671046102023000262220ustar 00000000000000// SEE ./README on how to edit this file [ ( "adapter.js" , "file_type_nest_adapter_js" ), ( "adapter.ts" , "file_type_nest_adapter_ts" ), ( "component.dart" , "file_type_ng_component_dart" ), ( "component.js" , "file_type_ng_component_js2" ), ( "component.ts" , "file_type_ng_component_ts2" ), ( "container.dart" , "file_type_ng_smart_component_dart" ), ( "container.js" , "file_type_ng_smart_component_js2" ), ( "container.ts" , "file_type_ng_smart_component_ts2" ), ( "controller.js" , "file_type_nest_controller_js" ), ( "controller.ts" , "file_type_nest_controller_ts" ), ( "css.map" , "file_type_cssmap" ), ( "d.ts" , "file_type_typescriptdef_official" ), ( "decorator.js" , "file_type_nest_decorator_js" ), ( "decorator.ts" , "file_type_nest_decorator_ts" ), ( "directive.dart" , "file_type_ng_directive_dart" ), ( "directive.js" , "file_type_ng_directive_js2" ), ( "directive.ts" , "file_type_ng_directive_ts2" ), ( "eslintrc.js" , "file_type_eslint" ), ( "filter.js" , "file_type_nest_filter_js" ), ( "filter.ts" , "file_type_nest_filter_ts" ), ( "gateway.js" , "file_type_nest_gateway_js" ), ( "gateway.ts" , "file_type_nest_gateway_ts" ), ( "gradle.kts" , "file_type_gradle" ), ( "guard.js" , "file_type_nest_guard_js" ), ( "guard.ts" , "file_type_nest_guard_ts" ), ( "interceptor.dart" , "file_type_ng_interceptor_dart" ), ( "interceptor.js" , "file_type_nest_interceptor_js" ), ( "interceptor.ts" , "file_type_nest_interceptor_ts" ), ( "js.flow" , "file_type_flow" ), ( "js.map" , "file_type_jsmap" ), ( "js.snap" , "file_type_jest_snapshot" ), ( "middleware.js" , "file_type_nest_middleware_js" ), ( "middleware.ts" , "file_type_nest_middleware_ts" ), ( "module.js" , "file_type_nest_module_js" ), ( "module.ts" , "file_type_nest_module_ts" ), ( "page.dart" , "file_type_ng_smart_component_dart" ), ( "page.js" , "file_type_ng_smart_component_js2" ), ( "page.ts" , "file_type_ng_smart_component_ts2" ), ( "pipe.dart" , "file_type_ng_pipe_dart" ), ( "pipe.js" , "file_type_nest_pipe_js" ), ( "pipe.ts" , "file_type_nest_pipe_ts" ), ( "routing.dart" , "file_type_ng_routing_dart" ), ( "routing.js" , "file_type_ng_routing_js2" ), ( "routing.ts" , "file_type_ng_routing_ts2" ), ( "service.dart" , "file_type_ng_service_dart" ), ( "service.js" , "file_type_nest_service_js" ), ( "service.ts" , "file_type_nest_service_ts" ), ( "tar.gz" , "file_type_zip" ), ( "tar.xz" , "file_type_zip" ), ( "tar.zst" , "file_type_zip" ), ] broot-1.46.3/resources/icons/nerdfont/data/extension_to_icon_name_map.rs000064400000000000000000001063031046102023000246550ustar 00000000000000// SEE ./README on how to edit this file [ ( "3g2" , "file_type_video" ), ( "3gp" , "file_type_video" ), ( "7z" , "file_type_zip2" ), ( "P" , "file_type_prolog" ), ( "a" , "file_type_binary" ), ( "aac" , "file_type_audio" ), ( "accda" , "file_type_access2" ), ( "accdb" , "file_type_access" ), ( "accdc" , "file_type_access2" ), ( "accde" , "file_type_access2" ), ( "accdp" , "file_type_access2" ), ( "accdr" , "file_type_access2" ), ( "accdt" , "file_type_access2" ), ( "accdu" , "file_type_access2" ), ( "act" , "file_type_audio" ), ( "ade" , "file_type_access2" ), ( "adp" , "file_type_access2" ), ( "afdesign" , "file_type_affinitydesigner" ), ( "affinitydesigner" , "file_type_affinitydesigner" ), ( "affinityphoto" , "file_type_affinityphoto" ), ( "affinitypublisher" , "file_type_affinitypublisher" ), ( "afphoto" , "file_type_affinityphoto" ), ( "afpub" , "file_type_affinitypublisher" ), ( "ai" , "file_type_ai2" ), ( "aiff" , "file_type_audio" ), ( "amr" , "file_type_audio" ), ( "amv" , "file_type_video" ), ( "ape" , "file_type_audio" ), ( "app" , "file_type_binary" ), ( "ascx" , "file_type_aspx" ), ( "asf" , "file_type_video" ), ( "aspx" , "file_type_aspx" ), ( "au" , "file_type_audio" ), ( "avi" , "file_type_video" ), ( "avif" , "file_type_avif" ), ( "awk" , "file_type_awk" ), ( "babelignore" , "file_type_babel2" ), ( "babelrc" , "file_type_babel2" ), ( "bazel" , "file_type_bazel" ), ( "bazelrc" , "file_type_bazel" ), ( "bb" , "file_type_blitzbasic" ), ( "bcmx" , "file_type_outlook" ), ( "bin" , "file_type_binary" ), ( "bithoundrc" , "file_type_bithound" ), ( "bmp" , "file_type_image" ), ( "boringignore" , "file_type_darcs" ), ( "bowerrc" , "file_type_bower" ), ( "browserslistrc" , "file_type_browserslist" ), ( "buckconfig" , "file_type_buckbuild" ), ( "bz" , "file_type_zip2" ), ( "bz2" , "file_type_zip2" ), ( "bzip2" , "file_type_zip2" ), ( "bzrignore" , "file_type_bazaar" ), ( "c" , "file_type_c" ), ( "cake" , "file_type_cake" ), ( "cargo-lock" , "file_type_rust" ), ( "cer" , "file_type_cert" ), ( "cfignore" , "file_type_cloudfoundry" ), ( "cjm" , "file_type_clojure" ), ( "cl" , "file_type_opencl" ), ( "class" , "file_type_class" ), ( "cljc" , "file_type_clojure" ), ( "cljs" , "file_type_clojurescript" ), ( "cma" , "file_type_binary" ), ( "cmi" , "file_type_binary" ), ( "cmo" , "file_type_binary" ), ( "cmx" , "file_type_binary" ), ( "cmxa" , "file_type_binary" ), ( "coffeelintignore" , "file_type_coffeelint" ), ( "condarc" , "file_type_conda" ), ( "cpp" , "file_type_cpp" ), ( "crec" , "file_type_lync" ), ( "crl" , "file_type_cert" ), ( "crt" , "file_type_cert" ), ( "cs" , "file_type_csharp" ), ( "csproj" , "file_type_csproj" ), ( "csr" , "file_type_cert" ), ( "css" , "file_type_css" ), ( "csslintrc" , "file_type_csslint" ), ( "csv" , "file_type_csv" ), ( "csx" , "file_type_csharp2" ), ( "cvsignore" , "file_type_cvs" ), ( "db" , "file_type_db" ), ( "db3" , "file_type_sqlite" ), ( "dct" , "file_type_audio" ), ( "der" , "file_type_cert" ), ( "dio" , "file_type_drawio" ), ( "divx" , "file_type_video" ), ( "djt" , "file_type_django" ), ( "dll" , "file_type_binary" ), ( "doc" , "file_type_word2" ), ( "docm" , "file_type_word2" ), ( "docx" , "file_type_word2" ), ( "doczrc" , "file_type_docz" ), ( "dojorc" , "file_type_dojo" ), ( "dot" , "file_type_word2" ), ( "dotm" , "file_type_word2" ), ( "dotx" , "file_type_word2" ), ( "drawio" , "file_type_drawio" ), ( "dss" , "file_type_audio" ), ( "dta" , "file_type_stata" ), ( "dvc" , "file_type_dvc" ), ( "dvf" , "file_type_audio" ), ( "eco" , "file_type_docpad" ), ( "editorconfig" , "file_type_editorconfig" ), ( "ejs" , "file_type_ejs" ), ( "el" , "file_type_emacs" ), ( "elc" , "file_type_emacs" ), ( "elm" , "file_type_elm" ), ( "ember-cli" , "file_type_ember" ), ( "enc" , "file_type_license" ), ( "ensime" , "file_type_ensime" ), ( "env" , "file_type_env" ), ( "eot" , "file_type_font" ), ( "eps" , "file_type_eps" ), ( "eskip" , "file_type_skipper" ), ( "eslintcache" , "file_type_eslint2" ), ( "eslintignore" , "file_type_eslint2" ), ( "eslintrc" , "file_type_eslint2" ), ( "exe" , "file_type_binary" ), ( "exp" , "file_type_tcl" ), ( "f4a" , "file_type_video" ), ( "f4b" , "file_type_video" ), ( "f4p" , "file_type_video" ), ( "f4v" , "file_type_video" ), ( "fbx" , "file_type_fbx" ), ( "fig" , "file_type_matlab" ), ( "firebaserc" , "file_type_firebase" ), ( "fish" , "file_type_shell" ), ( "fla" , "file_type_fla" ), ( "flac" , "file_type_audio" ), ( "flooignore" , "file_type_floobits" ), ( "flowconfig" , "file_type_flow" ), ( "flutter-plugins" , "file_type_flutter" ), ( "flv" , "file_type_video" ), ( "fods" , "file_type_excel2" ), ( "fossaignore" , "file_type_fossa" ), ( "fs" , "file_type_fsharp" ), ( "fsproj" , "file_type_fsproj" ), ( "gemfile" , "file_type_bundler" ), ( "gif" , "file_type_image" ), ( "gitattributes" , "file_type_git" ), ( "gitconfig" , "file_type_git" ), ( "gitignore" , "file_type_git" ), ( "gitkeep" , "file_type_git" ), ( "gitmodules" , "file_type_git" ), ( "gmx" , "file_type_gamemaker" ), ( "go" , "file_type_go" ), ( "gqlconfig" , "file_type_graphql" ), ( "gradle" , "file_type_gradle2" ), ( "graphqlconfig" , "file_type_graphql_config" ), ( "gsm" , "file_type_audio" ), ( "gvimrc" , "file_type_vim" ), ( "gz" , "file_type_zip2" ), ( "h" , "file_type_cheader" ), ( "hgignore" , "file_type_mercurial" ), ( "hl" , "file_type_binary" ), ( "hpp" , "file_type_cppheader" ), ( "hs" , "file_type_haskell" ), ( "html" , "file_type_html" ), ( "htmlhintrc" , "file_type_htmlhint" ), ( "huskyrc" , "file_type_husky" ), ( "hxp" , "file_type_lime" ), ( "hxproj" , "file_type_haxedevelop" ), ( "ibc" , "file_type_idrisbin" ), ( "ico" , "file_type_image" ), ( "idr" , "file_type_idris" ), ( "ignore-glob" , "file_type_fossil" ), ( "iklax" , "file_type_audio" ), ( "ilk" , "file_type_binary" ), ( "inc" , "file_type_inc" ), ( "include" , "file_type_inc" ), ( "infopathxml" , "file_type_infopath" ), ( "ino" , "file_type_arduino" ), ( "ipkg" , "file_type_idrispkg" ), ( "ipynb" , "file_type_jupyter" ), ( "iuml" , "file_type_plantuml" ), ( "ivs" , "file_type_audio" ), ( "jade-lintrc" , "file_type_pug" ), ( "jar" , "file_type_jar" ), ( "java" , "file_type_java" ), ( "jestrc" , "file_type_jest" ), ( "jpeg" , "file_type_image" ), ( "jpg" , "file_type_image" ), ( "jpmignore" , "file_type_jpm" ), ( "js" , "file_type_js" ), ( "jsbeautify" , "file_type_jsbeautify" ), ( "jsbeautifyrc" , "file_type_jsbeautify" ), ( "jshintignore" , "file_type_jshint" ), ( "jshintrc" , "file_type_jshint" ), ( "json" , "file_type_json_official" ), ( "json-ld" , "file_type_jsonld" ), ( "json5" , "file_type_json5" ), ( "jsonld" , "file_type_jsonld" ), ( "jsp" , "file_type_jsp" ), ( "jss" , "file_type_jss" ), ( "jl" , "file_type_julia" ), ( "kdl" , "file_type_config" ), ( "key" , "file_type_key" ), ( "kit" , "file_type_codekit" ), ( "kiteignore" , "file_type_kite" ), ( "kt" , "file_type_kotlin" ), ( "laccdb" , "file_type_access2" ), ( "ldb" , "file_type_access2" ), ( "lib" , "file_type_binary" ), ( "licence" , "file_type_license" ), ( "lidr" , "file_type_idris" ), ( "lintstagedrc" , "file_type_lintstagedrc" ), ( "liquid" , "file_type_liquid" ), ( "lnk" , "file_type_lnk" ), ( "lock" , "emoji_type_lock" ), ( "log" , "file_type_log" ), ( "ls" , "file_type_livescript" ), ( "lua" , "file_type_lua" ), ( "lucee" , "file_type_cf2" ), ( "m2v" , "file_type_video" ), ( "m4a" , "file_type_audio" ), ( "m4b" , "file_type_audio" ), ( "m4p" , "file_type_audio" ), ( "m4v" , "file_type_video" ), ( "mailmap" , "file_type_git" ), ( "mam" , "file_type_access2" ), ( "manifest" , "file_type_manifest" ), ( "map" , "file_type_map" ), ( "maq" , "file_type_access2" ), ( "markdown" , "file_type_markdown" ), ( "master" , "file_type_layout" ), ( "md" , "file_type_markdown" ), ( "mdb" , "file_type_access2" ), ( "mdown" , "file_type_markdown" ), ( "mdw" , "file_type_access2" ), ( "mdx" , "file_type_mdx" ), ( "merlin" , "file_type_ocaml" ), ( "metadata" , "file_type_flutter" ), ( "mex" , "file_type_matlab" ), ( "mexn" , "file_type_matlab" ), ( "mexrs6" , "file_type_matlab" ), ( "mk3d" , "file_type_video" ), ( "mkv" , "file_type_video" ), ( "mmf" , "file_type_audio" ), ( "mn" , "file_type_matlab" ), ( "mo" , "file_type_poedit" ), ( "modernizr" , "file_type_modernizr" ), ( "mogg" , "file_type_audio" ), ( "mov" , "file_type_video" ), ( "mp2" , "file_type_video" ), ( "mp3" , "file_type_audio" ), ( "mp4" , "file_type_video" ), ( "mpc" , "file_type_audio" ), ( "mpe" , "file_type_video" ), ( "mpeg" , "file_type_video" ), ( "mpeg2" , "file_type_video" ), ( "mpg" , "file_type_video" ), ( "mpv" , "file_type_video" ), ( "msg" , "file_type_outlook" ), ( "mst" , "file_type_mustache" ), ( "msv" , "file_type_audio" ), ( "mtn-ignore" , "file_type_monotone" ), ( "mum" , "file_type_matlab" ), ( "mustache" , "file_type_mustache" ), ( "mx" , "file_type_matlab" ), ( "mx3" , "file_type_matlab" ), ( "mysql" , "file_type_mysql" ), ( "n" , "file_type_binary" ), ( "ndll" , "file_type_binary" ), ( "nix" , "file_type_nix" ), ( "njs" , "file_type_nunjucks" ), ( "njsproj" , "file_type_njsproj" ), ( "node-version" , "file_type_node2" ), ( "nowignore" , "file_type_zeit" ), ( "npmignore" , "file_type_npm" ), ( "npmrc" , "file_type_npm" ), ( "npy" , "file_type_numpy" ), ( "npz" , "file_type_numpy" ), ( "nsriignore" , "file_type_nsri" ), ( "nsrirc" , "file_type_nsri" ), ( "nsv" , "file_type_video" ), ( "nu" , "file_type_nushell" ), ( "nunj" , "file_type_nunjucks" ), ( "nupkg" , "file_type_nuget" ), ( "nuspec" , "file_type_nuget" ), ( "nvmrc" , "file_type_node2" ), ( "nycrc" , "file_type_nyc" ), ( "o" , "file_type_binary" ), ( "obj" , "file_type_binary" ), ( "ocrec" , "file_type_lync" ), ( "ods" , "file_type_excel2" ), ( "oft" , "file_type_outlook" ), ( "oga" , "file_type_audio" ), ( "ogg" , "file_type_audio" ), ( "ogv" , "file_type_video" ), ( "one" , "file_type_onenote" ), ( "onepkg" , "file_type_onenote" ), ( "onetoc" , "file_type_onenote" ), ( "onetoc2" , "file_type_onenote" ), ( "opencl" , "file_type_opencl" ), ( "opus" , "file_type_audio" ), ( "org" , "file_type_org" ), ( "otf" , "file_type_font" ), ( "otm" , "file_type_outlook" ), ( "ovpn" , "file_type_ovpn" ), ( "p12" , "file_type_cert" ), ( "p4ignore" , "file_type_helix" ), ( "p7b" , "file_type_cert" ), ( "p7r" , "file_type_cert" ), ( "pa" , "file_type_powerpoint2" ), ( "packages" , "file_type_flutter_package" ), ( "patch" , "file_type_patch" ), ( "pcd" , "file_type_pcl" ), ( "pck" , "file_type_plsql_package" ), ( "pdb" , "file_type_binary" ), ( "pde" , "file_type_arduino" ), ( "pdf" , "file_type_pdf2" ), ( "pem" , "file_type_key" ), ( "pex" , "file_type_xml" ), ( "pfa" , "file_type_font" ), ( "pfb" , "file_type_font" ), ( "pfx" , "file_type_cert" ), ( "pgsql" , "file_type_pgsql" ), ( "phar" , "file_type_php3" ), ( "php" , "file_type_php" ), ( "php1" , "file_type_php3" ), ( "php2" , "file_type_php3" ), ( "php3" , "file_type_php3" ), ( "php4" , "file_type_php3" ), ( "php5" , "file_type_php3" ), ( "php6" , "file_type_php3" ), ( "php_cs" , "file_type_phpcsfixer" ), ( "phps" , "file_type_php3" ), ( "phpsa" , "file_type_php3" ), ( "phpt" , "file_type_php3" ), ( "phpunit" , "file_type_phpunit" ), ( "phtml" , "file_type_php3" ), ( "pipfile" , "file_type_pip" ), ( "pkb" , "file_type_plsql_package_body" ), ( "pkg" , "file_type_package" ), ( "pkh" , "file_type_plsql_package_header" ), ( "pks" , "file_type_plsql_package_spec" ), ( "plantuml" , "file_type_plantuml" ), ( "plist" , "file_type_config" ), ( "png" , "file_type_image" ), ( "po" , "file_type_poedit" ), ( "policyfile" , "file_type_chef" ), ( "postcssrc" , "file_type_postcssconfig" ), ( "pot" , "file_type_powerpoint2" ), ( "potm" , "file_type_powerpoint2" ), ( "potx" , "file_type_powerpoint2" ), ( "ppa" , "file_type_powerpoint2" ), ( "ppam" , "file_type_powerpoint2" ), ( "pps" , "file_type_powerpoint2" ), ( "ppsm" , "file_type_powerpoint2" ), ( "ppsx" , "file_type_powerpoint2" ), ( "ppt" , "file_type_powerpoint2" ), ( "pptm" , "file_type_powerpoint2" ), ( "pptx" , "file_type_powerpoint2" ), ( "prettierignore" , "file_type_prettier" ), ( "prettierrc" , "file_type_prettier" ), ( "prisma" , "file_type_prisma" ), ( "pro" , "file_type_prolog" ), ( "procfile" , "file_type_procfile" ), ( "psd" , "file_type_photoshop2" ), ( "psd1" , "file_type_powershell_psd2" ), ( "psm1" , "file_type_powershell_psm2" ), ( "psmdcp" , "file_type_nuget" ), ( "pst" , "file_type_outlook" ), ( "pu" , "file_type_plantuml" ), ( "pub" , "file_type_publisher" ), ( "pug-lintrc" , "file_type_pug" ), ( "puml" , "file_type_plantuml" ), ( "puz" , "file_type_publisher" ), ( "py" , "file_type_python" ), ( "pyc" , "file_type_binary" ), ( "pyd" , "file_type_binary" ), ( "pyo" , "file_type_binary" ), ( "pyup" , "file_type_pyup" ), ( "q" , "file_type_q" ), ( "qbs" , "file_type_qbs" ), ( "qmldir" , "file_type_qmldir" ), ( "qt" , "file_type_video" ), ( "qvd" , "file_type_qlikview" ), ( "qvw" , "file_type_qlikview" ), ( "r" , "file_type_r" ), ( "ra" , "file_type_audio" ), ( "rake" , "file_type_rake" ), ( "rakefile" , "file_type_rake" ), ( "rar" , "file_type_zip2" ), ( "raw" , "file_type_audio" ), ( "rb" , "file_type_ruby" ), ( "re" , "file_type_reason" ), ( "reg" , "file_type_registry" ), ( "rego" , "file_type_rego" ), ( "rehypeignore" , "file_type_rehype" ), ( "rehyperc" , "file_type_rehype" ), ( "remarkignore" , "file_type_remark" ), ( "remarkrc" , "file_type_remark" ), ( "renovaterc" , "file_type_renovate" ), ( "retextignore" , "file_type_retext" ), ( "retextrc" , "file_type_retext" ), ( "rm" , "file_type_video" ), ( "rmvb" , "file_type_video" ), ( "rproj" , "file_type_rproj" ), ( "rs" , "file_type_rust" ), ( "rspec" , "file_type_rspec" ), ( "rt" , "file_type_reacttemplate" ), ( "rust-toolchain" , "file_type_rust_toolchain" ), ( "rwd" , "file_type_matlab" ), ( "sailsrc" , "file_type_sails" ), ( "sass" , "file_type_sass" ), ( "sbt" , "file_type_sbt" ), ( "scala" , "file_type_scala" ), ( "scpt" , "file_type_binary" ), ( "scptd" , "file_type_binary" ), ( "scssm" , "file_type_scss" ), ( "sentryclirc" , "file_type_sentry" ), ( "sequelizerc" , "file_type_sequelize" ), ( "sfd" , "file_type_font" ), ( "sh" , "file_type_shell" ), ( "sig" , "file_type_onenote" ), ( "sketch" , "file_type_sketch" ), ( "slddc" , "file_type_matlab" ), ( "sldm" , "file_type_powerpoint2" ), ( "sldx" , "file_type_powerpoint2" ), ( "sln" , "file_type_sln2" ), ( "sls" , "file_type_saltstack" ), ( "slx" , "file_type_matlab" ), ( "smv" , "file_type_matlab" ), ( "snyk" , "file_type_snyk" ), ( "so" , "file_type_binary" ), ( "solidarity" , "file_type_solidarity" ), ( "spe" , "file_type_spacengine" ), ( "sql" , "file_type_sql" ), ( "sqlite" , "file_type_sqlite" ), ( "sqlite3" , "file_type_sqlite" ), ( "src" , "file_type_cert" ), ( "sss" , "file_type_sss" ), ( "sst" , "file_type_cert" ), ( "stl" , "file_type_cert" ), ( "storyboard" , "file_type_storyboard" ), ( "stylelintcache" , "file_type_stylelint" ), ( "stylelintignore" , "file_type_stylelint" ), ( "stylelintrc" , "file_type_stylelint" ), ( "svg" , "file_type_svg" ), ( "svi" , "file_type_video" ), ( "svnignore" , "file_type_subversion" ), ( "swc" , "file_type_flash" ), ( "swf" , "file_type_flash" ), ( "swift" , "file_type_swift" ), ( "tar" , "file_type_zip2" ), ( "tcl" , "file_type_tcl" ), ( "texi" , "file_type_tex" ), ( "tf" , "file_type_terraform" ), ( "tfignore" , "file_type_tfs" ), ( "tfstate" , "file_type_terraform" ), ( "tgz" , "file_type_zip2" ), ( "tiff" , "file_type_image" ), ( "tikz" , "file_type_tex" ), ( "tlg" , "file_type_log" ), ( "tmlanguage" , "file_type_xml" ), ( "todo" , "file_type_todo" ), ( "toml" , "file_type_toml" ), ( "ts" , "file_type_typescript" ), ( "tst" , "file_type_test" ), ( "tt2" , "file_type_tt" ), ( "tta" , "file_type_audio" ), ( "ttf" , "file_type_font" ), ( "txt" , "file_type_text" ), ( "unibeautifyrc" , "file_type_unibeautify" ), ( "unity" , "file_type_shaderlab" ), ( "vagrantfile" , "file_type_vagrant" ), ( "vala" , "file_type_vala" ), ( "vapi" , "file_type_vapi" ), ( "vash" , "file_type_vash" ), ( "vbhtml" , "file_type_vbhtml" ), ( "vbproj" , "file_type_vbproj" ), ( "vcxproj" , "file_type_vcxproj" ), ( "vercelignore" , "file_type_zeit" ), ( "vimrc" , "file_type_vim" ), ( "vob" , "file_type_video" ), ( "vox" , "file_type_audio" ), ( "vscodeignore" , "file_type_vscode-insiders" ), ( "vsix" , "file_type_vsix" ), ( "vsixmanifest" , "file_type_vsixmanifest" ), ( "vuerc" , "file_type_vueconfig" ), ( "wasm" , "file_type_wasm" ), ( "watchmanconfig" , "file_type_watchmanconfig" ), ( "wav" , "file_type_audio" ), ( "webm" , "file_type_video" ), ( "webp" , "file_type_webp" ), ( "wll" , "file_type_word2" ), ( "wma" , "file_type_audio" ), ( "wmv" , "file_type_video" ), ( "woff" , "file_type_font" ), ( "woff2" , "file_type_font" ), ( "wxml" , "file_type_wxml" ), ( "wxss" , "file_type_wxss" ), ( "xcodeproj" , "file_type_xcode" ), ( "xfl" , "file_type_xfl" ), ( "xib" , "file_type_xib" ), ( "xlf" , "file_type_xliff" ), ( "xliff" , "file_type_xliff" ), ( "xls" , "file_type_excel2" ), ( "xlsm" , "file_type_excel2" ), ( "xlsx" , "file_type_excel2" ), ( "xsf" , "file_type_infopath" ), ( "xsn" , "file_type_infopath" ), ( "xtp2" , "file_type_infopath" ), ( "xvc" , "file_type_matlab" ), ( "xz" , "file_type_zip2" ), ( "yaml" , "file_type_yaml" ), ( "yamllint" , "file_type_yamllint" ), ( "yarn-integrity" , "file_type_yarn" ), ( "yarnclean" , "file_type_yarn" ), ( "yarnignore" , "file_type_yarn" ), ( "yarnrc" , "file_type_yarn" ), ( "yaspellerrc" , "file_type_yandex" ), ( "yml" , "file_type_yaml" ), ( "yy" , "file_type_gamemaker2" ), ( "yyp" , "file_type_gamemaker2" ), ( "zip" , "file_type_zip2" ), ( "zipx" , "file_type_zip2" ), ( "zst" , "file_type_zip2" ), // SEE ./README on how to edit this file ] broot-1.46.3/resources/icons/nerdfont/data/file_name_to_icon_name_map.rs000064400000000000000000000321321046102023000245560ustar 00000000000000[ ( ".scalafix.conf" , "file_type_config" ), ( ".scalafmt.conf" , "file_type_config" ), ( "LICENCE" , "file_type_license" ), ( "LICENSE" , "file_type_license" ), ( "VERSION" , "file_type_version" ), ( "licence" , "file_type_license" ), ( "license" , "file_type_license" ), ( "readme" , "file_type_text" ), ( "todo" , "file_type_todo" ), ( "version" , "file_type_version" ), ( "angular-cli.json" , "file_type_angular" ), ( "angular.json" , "file_type_angular" ), ( "api-extractor-base.json" , "file_type_api_extractor" ), ( "api-extractor.json" , "file_type_api_extractor" ), ( "app-routing.module.dart" , "file_type_ng_routing_dart" ), ( "app-routing.module.js" , "file_type_ng_routing_js2" ), ( "app-routing.module.ts" , "file_type_ng_routing_ts2" ), ( "app.config.js" , "file_type_expo" ), ( "app.config.json" , "file_type_expo" ), ( "app.config.json5" , "file_type_expo" ), ( "app.json" , "file_type_expo" ), ( "appveyor.yml" , "file_type_appveyor" ), ( "aurelia.json" , "file_type_aurelia" ), ( "azure-pipelines.yml" , "file_type_azurepipelines" ), ( "bazel.rc" , "file_type_bazel" ), ( "berksfile" , "file_type_chef" ), ( "berksfile.lock" , "file_type_chef" ), ( "bitbucket-pipelines.yml" , "file_type_bitbucketpipeline" ), ( "bower.json" , "file_type_bower" ), ( "browserslist" , "file_type_browserslist" ), ( "build.ninja" , "file_type_ninja" ), ( "build.properties" , "file_type_config" ), ( "cargo.lock" , "file_type_cargo" ), ( "cargo.toml" , "file_type_cargo" ), ( "checkstyle.json" , "file_type_haxecheckstyle" ), ( "chefignore" , "file_type_chef" ), ( "circle.yml" , "file_type_circleci" ), ( "codacy.yaml" , "file_type_codacy" ), ( "codacy.yml" , "file_type_codacy" ), ( "codeclimate.yml" , "file_type_codeclimate" ), ( "codecov.yml" , "file_type_codecov" ), ( "coffeelint.json" , "file_type_coffeelint" ), ( "commitlint.config.js" , "file_type_commitlint" ), ( "composer.json" , "file_type_composer" ), ( "composer.lock" , "file_type_composer" ), ( "conanfile.py" , "file_type_conan" ), ( "conanfile.txt" , "file_type_conan" ), ( "config.codekit" , "file_type_codekit" ), ( "config.codekit2" , "file_type_codekit" ), ( "config.codekit3" , "file_type_codekit" ), ( "coveralls.yml" , "file_type_coveralls" ), ( "crowdin.yml" , "file_type_crowdin" ), ( "csscomb.json" , "file_type_csscomb" ), ( "dependabot.yml" , "file_type_dependabot" ), ( "dependencies.yml" , "file_type_dependencies" ), ( "devcontainer.json" , "file_type_devcontainer" ), ( "docker-compose.test.yml" , "file_type_dockertest2" ), ( "drone.yml" , "file_type_drone" ), ( "drone.yml.sig" , "file_type_drone" ), ( "ejs.t" , "file_type_hygen" ), ( "elm-package.json" , "file_type_elm2" ), ( "emakefile" , "file_type_erlang2" ), ( "emakerfile" , "file_type_erlang2" ), ( "eslint.config.js" , "file_type_eslint" ), ( "eslint.config.cjs" , "file_type_eslint" ), ( "eslint.config.mjs" , "file_type_eslint" ), ( "favicon.ico" , "file_type_favicon" ), ( "firebase.json" , "file_type_firebasehosting" ), ( "firestore.indexes.json" , "file_type_firestore" ), ( "firestore.rules" , "file_type_firestore" ), ( "format.ps1xml" , "file_type_powershell_format" ), ( "fuse.js" , "file_type_fusebox" ), ( "gemfile.lock" , "file_type_bundler" ), ( "gitlab-ci.yml" , "file_type_gitlab" ), ( "glide.yml" , "file_type_glide" ), ( "go.mod" , "file_type_go_package" ), ( "go.sum" , "file_type_go_package" ), ( "greenkeeper.json" , "file_type_greenkeeper" ), ( "guard.dart" , "file_type_ng_guard_dart" ), ( "haxelib.json" , "file_type_haxe" ), ( "husky.config.js" , "file_type_husky" ), ( "include.xml" , "file_type_lime" ), ( "integrity.json" , "file_type_nsri-integrity" ), ( "ionic.config.json" , "file_type_ionic" ), ( "ionic.project" , "file_type_ionic" ), ( "jade-lint.json" , "file_type_pug" ), ( "jakefile" , "file_type_jake" ), ( "jakefile.js" , "file_type_jake" ), ( "jasmine.json" , "file_type_jasmine" ), ( "jbuilder" , "file_type_jbuilder" ), ( "jest.config.json" , "file_type_jest" ), ( "jest.json" , "file_type_jest" ), ( "jestrc.js" , "file_type_jest" ), ( "jestrc.json" , "file_type_jest" ), ( "jsconfig.json" , "file_type_jsconfig" ), ( "jscpd.json" , "file_type_jscpd" ), ( "jsx.snap" , "file_type_jest_snapshot" ), ( "kitchen.yml" , "file_type_kitchenci" ), ( "layout.htm" , "file_type_layout" ), ( "layout.html" , "file_type_layout" ), ( "lerna.json" , "file_type_lerna" ), ( "lint-staged.config.js" , "file_type_lintstagedrc" ), ( "makefile" , "file_type_makefile" ), ( "manifest.bak" , "file_type_manifest_bak" ), ( "manifest.skip" , "file_type_manifest_skip" ), ( "markdownlint.json" , "file_type_markdownlint" ), ( "marko.js" , "file_type_markojs" ), ( "maven.config" , "file_type_maven" ), ( "mocha.opts" , "file_type_mocha" ), ( "module.dart" , "file_type_ng_module_dart" ), ( "nest-cli.json" , "file_type_nestjs" ), ( "nestconfig.json" , "file_type_nestjs" ), ( "netlify.toml" , "file_type_netlify" ), ( "next.config.js" , "file_type_next" ), ( "ng-tailwind.js" , "file_type_ng_tailwind" ), ( "nginx.conf" , "file_type_nginx" ), ( "nodemon.json" , "file_type_nodemon" ), ( "now.json" , "file_type_zeit" ), ( "npm-shrinkwrap.json" , "file_type_npm" ), ( "nsri.config.js" , "file_type_nsri" ), ( "nycrc.json" , "file_type_nyc" ), ( "package-lock.json" , "file_type_npm" ), ( "package.json" , "file_type_npm" ), ( "package.pins" , "file_type_swift" ), ( "php_cs.dist" , "file_type_phpcsfixer" ), ( "phpunit.xml" , "file_type_phpunit" ), ( "phpunit.xml.dist" , "file_type_phpunit" ), ( "phraseapp.yml" , "file_type_phraseapp" ), ( "pipfile.lock" , "file_type_pip" ), ( "platformio.ini" , "file_type_platformio" ), ( "pnpm-lock.yaml" , "file_type_pnpm" ), ( "pnpm-workspace.yaml" , "file_type_pnpm" ), ( "pnpmfile.js" , "file_type_pnpm" ), ( "postcss.config.js" , "file_type_postcssconfig" ), ( "postcssrc.js" , "file_type_postcssconfig" ), ( "postcssrc.json" , "file_type_postcssconfig" ), ( "postcssrc.yml" , "file_type_postcssconfig" ), ( "pre-commit-config.yaml" , "file_type_precommit" ), ( "pubspec.lock" , "file_type_flutter_package" ), ( "pubspec.yaml" , "file_type_flutter_package" ), ( "pug-lintrc.js" , "file_type_pug" ), ( "pug-lintrc.json" , "file_type_pug" ), ( "pyup.yml" , "file_type_pyup" ), ( "quasar.conf.js" , "file_type_quasar" ), ( "robots.txt" , "file_type_robots" ), ( "rubocop.yml" , "file_type_rubocop" ), ( "rubocop_todo.yml" , "file_type_rubocop" ), ( "serverless.yml" , "file_type_serverless" ), ( "snapcraft.yaml" , "file_type_snapcraft" ), ( "solidarity.json" , "file_type_solidarity" ), ( "stylish-haskell.yaml" , "file_type_stylish_haskell" ), ( "svelte.config.js" , "file_type_svelte" ), ( "symfony.lock" , "file_type_symfony" ), ( "tailwind.config.cjs" , "file_type_tailwind" ), ( "tailwind.config.js" , "file_type_tailwind" ), ( "testcaferc.json" , "file_type_testcafe" ), ( "tox.ini" , "file_type_tox" ), ( "travis.yml" , "file_type_travis" ), ( "ts.snap" , "file_type_jest_snapshot" ), ( "tslint.json" , "file_type_tslint" ), ( "tslint.yaml" , "file_type_tslint" ), ( "tslint.yml" , "file_type_tslint" ), ( "tsx.snap" , "file_type_jest_snapshot" ), ( "types.ps1xml" , "file_type_powershell_types" ), ( "unibeautify.config.js" , "file_type_unibeautify" ), ( "vercel.json" , "file_type_zeit" ), ( "vsts-ci.yml" , "file_type_azurepipelines" ), ( "vue.config.js" , "file_type_vueconfig" ), ( "wercker.yml" , "file_type_wercker" ), ( "wpml-config.xml" , "file_type_wpml" ), ( "yarn-metadata.json" , "file_type_yarn" ), ( "yarn.lock" , "file_type_yarn" ), ( "yaspeller.json" , "file_type_yandex" ), ( "yo-rc.json" , "file_type_yeoman" ), ] broot-1.46.3/resources/icons/nerdfont/data/icon_name_to_icon_code_point_map.rs000064400000000000000000000720351046102023000260000ustar 00000000000000// if you want to find an icon for a file: go to https://www.nerdfonts.com/cheat-sheet and search // for f07b in case of "default_file". That should return:  [ ( "default_file", 0xf15b ), //  ( "default_folder", 0xf07b ), //  ( "default_folder_opened", 0xf07c ), //  ( "default_root_folder", 0xea83 ), //  ( "default_root_folder_opened", 0xf115 ), //  ( "emoji_type_link", 0xf0c1 ), //  ( "emoji_type_lock", 0xf023 ), //  ( "file_type_access", 0xf0221 ), // 󰈡 ( "file_type_access2", 0xf1aa1 ), // 󱪡 ( "file_type_actionscript", 0xeaff ), //  ( "file_type_actionscript2", 0xeaff ), //  ( "file_type_ada", 0xf15b ), ( "file_type_advpl", 0xf15b ), ( "file_type_affectscript", 0xf0477 ), // 󰑷 ( "file_type_affinitydesigner", 0xf1c5 ), //  ( "file_type_affinityphoto", 0xf1c5 ), //  ( "file_type_affinitypublisher", 0xf1c5 ), //  ( "file_type_ai", 0xe669 ), //  ( "file_type_ai2", 0xe7b4 ), //  ( "file_type_al", 0xf061d ), // 󰘝 ( "file_type_angular", 0xe753 ), //  ( "file_type_ansible", 0xf109a ), // 󱂚 ( "file_type_antlr", 0xf1119 ), // 󱄙 ( "file_type_anyscript", 0xf0477 ), // 󰑷 ( "file_type_apache", 0xf048b ), // 󰒋 ( "file_type_apex", 0xf17b ), //  ( "file_type_api_extractor", 0xf109b ), // 󱂛 ( "file_type_apib", 0xf109b ), // 󱂛 ( "file_type_apib2", 0xf109b ), // 󱂛 ( "file_type_apl", 0xe638 ), //  ( "file_type_applescript", 0xe711 ), //  ( "file_type_appveyor", 0xeacf ), //  ( "file_type_arduino", 0xf043f ), // 󰐿 ( "file_type_asciidoc", 0xf09ee ), // 󰧮 ( "file_type_asp", 0xf0aae ), // 󰪮 ( "file_type_aspx", 0xf0aae ), // 󰪮 ( "file_type_assembly", 0xe637 ), //  ( "file_type_ats", 0xf107c ), // 󱁼 ( "file_type_audio", 0xf0223 ), // 󰈣 ( "file_type_aurelia", 0xf022a ), // 󰈪 ( "file_type_autohotkey", 0xf107b ), // 󰷊 ( "file_type_autoit", 0xf107b ), // 󰷊 ( "file_type_avif", 0xf021f ), // 󰈟 ( "file_type_avro", 0xf09ee ), // 󰧮 ( "file_type_awk", 0xf09ee ), // 󰧮 ( "file_type_aws", 0xf102a ), // 󱀪 ( "file_type_azure", 0xf102a ), // 󱀪 ( "file_type_azurepipelines", 0xf102a ), // 󱀪 ( "file_type_babel", 0xf0a25 ), // 󰨥 ( "file_type_babel2", 0xf0a25 ), // 󰨥 ( "file_type_ballerina", 0xf15ca ), // 󱗊 ( "file_type_bat", 0xf0b5f ), // 󰭟 ( "file_type_bats", 0xf0b5f ), // 󰭟 ( "file_type_bazaar", 0xf0d6 ), //  ( "file_type_bazel", 0xe63a ), //  ( "file_type_befunge", 0xf04cc ), // 󰓌 ( "file_type_biml", 0xe73e ), //  ( "file_type_binary", 0xeae8 ), //  ( "file_type_bitbucketpipeline", 0xe703 ), //  ( "file_type_bithound", 0xf0a44 ), // 󰩄 ( "file_type_blade", 0xf0e61 ), // 󰹡 ( "file_type_blitzbasic", 0xf0e7 ), //  ( "file_type_bolt", 0xf0e7 ), //  ( "file_type_bosque", 0xf021b ), // 󰈛 ( "file_type_bower", 0xe74d ), //  ( "file_type_bower2", 0xe74d ), //  ( "file_type_browserslist", 0xf488 ), //  ( "file_type_buckbuild", 0xf1415 ), // 󱐕 ( "file_type_bundler", 0xf107c ), // 󱁼 ( "file_type_c", 0xe649 ), //  ( "file_type_c2", 0xf107c ), // 󱁼 ( "file_type_c3", 0xf107c ), // 󱁼 ( "file_type_c_al", 0xf107c ), // 󱁼 ( "file_type_cabal", 0xe777 ), //  ( "file_type_caddy", 0xf107c ), // 󱁼 ( "file_type_cake", 0xe63d ), //  ( "file_type_cakephp", 0xe63d ), //  ( "file_type_capacitor", 0xf107c ), // 󱁼 ( "file_type_cargo", 0xe68b ), //  ( "file_type_cddl", 0xf107c ), // 󱁼 ( "file_type_cert", 0xf1186 ), // 󱆆 ( "file_type_ceylon", 0xf107c ), // 󱁼 ( "file_type_cf", 0xf107c ), // 󱁼 ( "file_type_cf2", 0xf107c ), // 󱁼 ( "file_type_cfc", 0xf107c ), // 󱁼 ( "file_type_cfc2", 0xf107c ), // 󱁼 ( "file_type_cfm", 0xf107c ), // 󱁼 ( "file_type_cfm2", 0xf107c ), // 󱁼 ( "file_type_cheader", 0xf0273 ), // 󰉳 ( "file_type_chef", 0xf107c ), // 󱁼 ( "file_type_chef_cookbook", 0xf107c), // 󱁼 ( "file_type_circleci", 0xf107c ), // 󱁼 ( "file_type_class", 0xeb5b ), //  ( "file_type_clojure", 0xe642 ), //  ( "file_type_clojurescript", 0xe768 ), //  ( "file_type_cloudfoundry", 0xf0217 ), // 󰈗 ( "file_type_cmake", 0xe673 ), //  ( "file_type_cobol", 0xf107c ), // 󱁼 ( "file_type_codacy", 0xf017c ), // 󱁼 ( "file_type_codeclimate", 0xf017c ), // 󱁼 ( "file_type_codecov", 0xf017c ), // 󱁼 ( "file_type_codekit", 0xeae9 ), //  ( "file_type_coffeelint", 0xf06ca ), // 󰛊 ( "file_type_coffeescript", 0xe751 ), //  ( "file_type_commitlint", 0xe729 ), //  ( "file_type_compass", 0xebd5 ), //  ( "file_type_composer", 0xe783 ), //  ( "file_type_conan", 0xf107c ), // 󱁼 ( "file_type_conda", 0xf107c ), // 󱁼 ( "file_type_config", 0xe615 ), //  ( "file_type_confluence", 0xf0303 ), // 󰌃 ( "file_type_coveralls", 0xf107c ), // 󱁼 ( "file_type_cpp", 0xe646 ), //  ( "file_type_cpp2", 0xe61d ), //  ( "file_type_cpp3", 0xf0672 ), // 󰙲 ( "file_type_cppheader", 0xf0273 ), // 󰉳 ( "file_type_crowdin", 0xf1975 ), // 󱥵 ( "file_type_crystal", 0xe62f ), //  ( "file_type_csharp", 0xe648 ), //  ( "file_type_csharp2", 0xe648 ), //  ( "file_type_csproj", 0xeb51 ), //  ( "file_type_css", 0xe749), //  ( "file_type_csscomb", 0xe614 ), //  ( "file_type_csslint", 0xe614 ), //  ( "file_type_cssmap", 0xe614 ), //  ( "file_type_csv", 0xe64a ), //  ( "file_type_cucumber", 0xf017c ), // 󱁼 ( "file_type_cuda", 0xe64b ), //  ( "file_type_cvs", 0xf017c ), // 󱁼 ( "file_type_cypress", 0xf0c35 ), // 󰰵 ( "file_type_cython", 0xe73c ), //  ( "file_type_dal", 0xf15b ), ( "file_type_darcs", 0xf15b ), ( "file_type_dartlang", 0xe798 ), //  ( "file_type_db", 0xe64d ), //  ( "file_type_delphi", 0xf15b ), ( "file_type_dependabot", 0xf0573 ), // 󰕳 ( "file_type_dependencies", 0xf0573 ), // 󰕳 ( "file_type_devcontainer", 0xf15b ), ( "file_type_diff", 0xf055a ), // 󰕚 ( "file_type_django", 0xe71d ), //  ( "file_type_dlang", 0xe7af ), //  ( "file_type_docker", 0xf308 ), //  ( "file_type_docker2", 0xe7b0 ), //  ( "file_type_dockertest", 0xe650 ), //  ( "file_type_dockertest2", 0xf0868 ), // 󰡨 ( "file_type_docpad", 0xf1a9a ), // 󱪚 ( "file_type_docz", 0xf0dc9 ), // 󰷉 ( "file_type_dojo", 0xe71c ), //  ( "file_type_dotjs", 0xf444 ), //  ( "file_type_doxygen", 0xf15b ), ( "file_type_drawio", 0xf0f49 ), // 󰽉 ( "file_type_drone", 0xf01e2 ), // 󰇢 ( "file_type_drools", 0xf15b ), ( "file_type_dustjs", 0xe35d ), //  ( "file_type_dvc", 0xf15b ), ( "file_type_dylan", 0xf15b ), ( "file_type_edge", 0xf01e9 ), // 󰇩 ( "file_type_edge2", 0xf282 ), //  ( "file_type_editorconfig", 0xe652 ), //  ( "file_type_eex", 0xf15b ), ( "file_type_ejs", 0xe618 ), //  ( "file_type_elastic", 0xea6d ), //  ( "file_type_elasticbeanstalk", 0xe26a ), //  ( "file_type_elixir", 0xe62d ), //  ( "file_type_elm", 0xe62c ), //  ( "file_type_elm2", 0xe62c ), //  ( "file_type_emacs", 0xe632 ), //  ( "file_type_ember", 0xe71b ), //  ( "file_type_ensime", 0xf15b ), ( "file_type_env", 0xe615 ), //  ( "file_type_eps", 0xf10e0 ), // 󱃠 ( "file_type_erb", 0xf15b ), ( "file_type_erlang", 0xe7b1 ), //  ( "file_type_erlang2", 0xe7b1 ), //  ( "file_type_eslint", 0xf0c7a ), // 󰱺 ( "file_type_eslint2", 0xf0c7a ), // 󰱺 ( "file_type_excel", 0xf021b ), // 󰈛 ( "file_type_excel2", 0xf021b ), // 󰈛 ( "file_type_expo", 0xe27c ), //  ( "file_type_falcon", 0xf15b ), ( "file_type_favicon", 0xe623 ), //  ( "file_type_fbx", 0xf15b ), ( "file_type_firebase", 0xf0967 ), // 󰥧 ( "file_type_firebasehosting", 0xe787 ), //  ( "file_type_firestore", 0xe657 ), //  ( "file_type_fla", 0xf15b ), ( "file_type_flash", 0xf0e7 ), //  ( "file_type_floobits", 0xf15b ), ( "file_type_flow", 0xf15b ), ( "file_type_flutter", 0xf15b ), ( "file_type_flutter_package", 0xf487 ), //  ( "file_type_font", 0xf031 ), //  ( "file_type_fortran", 0xf121a ), // 󱈚 ( "file_type_fossa", 0xf15b ), ( "file_type_fossil", 0xf15b ), ( "file_type_freemarker", 0xf15b ), ( "file_type_fsharp", 0xe65a ), //  ( "file_type_fsharp2", 0xe65a ), //  ( "file_type_fsproj", 0xe65a ), //  ( "file_type_fthtml", 0xf0f6d ), // 󰽭 ( "file_type_fusebox", 0xf15b ), ( "file_type_galen", 0xf15b ), ( "file_type_galen2", 0xf15b ), ( "file_type_gamemaker", 0xf0eb7 ), // 󰺷 ( "file_type_gamemaker2", 0xf1393 ), // 󱎓 ( "file_type_gamemaker81", 0xf11b ), //  ( "file_type_gatsby", 0xf0e43 ), // 󰹃 ( "file_type_gcode", 0xf15b ), ( "file_type_genstat", 0xf15b ), ( "file_type_git", 0xe702 ), //  ( "file_type_git2", 0xe702 ), //  ( "file_type_gitlab", 0xf296 ), //  ( "file_type_glide", 0xf2a5 ), //  ( "file_type_glsl", 0xf0dcb ), // 󰷋 ( "file_type_glyphs", 0xf15b ), ( "file_type_gnuplot", 0xe779 ), //  ( "file_type_go", 0xe626 ), //  ( "file_type_go_aqua", 0xf15b ), ( "file_type_go_black", 0xf15b ), ( "file_type_go_fuchsia", 0xe626 ), //  ( "file_type_go_gopher", 0xe626 ), //  ( "file_type_go_lightblue", 0xe626 ), //  ( "file_type_go_package", 0xe626 ), //  ( "file_type_go_white", 0xe626 ), //  ( "file_type_go_yellow", 0xe626 ), //  ( "file_type_godot", 0xe65f ), //  ( "file_type_gradle", 0xe660 ), //  ( "file_type_gradle2", 0xe660 ), //  ( "file_type_graphql", 0xf0877 ), // 󰡷 ( "file_type_graphql_config", 0xf0877 ), // 󰡷 ( "file_type_graphviz", 0xeb03 ), //  ( "file_type_greenkeeper", 0xf15b ), ( "file_type_gridsome", 0xf15b ), ( "file_type_groovy", 0xe775 ), //  ( "file_type_groovy2", 0xe775 ), //  ( "file_type_grunt", 0xe74c ), //  ( "file_type_gulp", 0xe763 ), //  ( "file_type_haml", 0xe664 ), //  ( "file_type_handlebars", 0xe60e ), //  ( "file_type_handlebars2", 0xe60e ), //  ( "file_type_harbour", 0xf15b ), ( "file_type_haskell", 0xe777 ), //  ( "file_type_haskell2", 0xe777 ), //  ( "file_type_haxe", 0xe666 ), //  ( "file_type_haxecheckstyle", 0xe666 ), //  ( "file_type_haxedevelop", 0xe666 ), //  ( "file_type_helix", 0xf15b ), ( "file_type_helm", 0xf15b ), ( "file_type_hjson", 0xe60b ), //  ( "file_type_hlsl", 0xf0dcb ), // 󰷋 ( "file_type_homeassistant", 0xf07d0 ), // 󰟐 ( "file_type_host", 0xe0a2 ), //  ( "file_type_htaccess", 0xe615 ), //  ( "file_type_html", 0xe736 ), //  ( "file_type_htmlhint", 0xe736 ), //  ( "file_type_http", 0xf15b ), ( "file_type_hunspell", 0xf15b ), ( "file_type_husky", 0xf107c ), ( "file_type_hy", 0xf15b ), ( "file_type_hygen", 0xf15b ), ( "file_type_icl", 0xf15b ), ( "file_type_idris", 0xf15b ), ( "file_type_idrisbin", 0xf15b ), ( "file_type_idrispkg", 0xf15b ), ( "file_type_image", 0xf021f ), // 󰈟 ( "file_type_imba", 0xf15b ), ( "file_type_inc", 0xf15b ), ( "file_type_infopath", 0xf15b ), ( "file_type_informix", 0xf15b ), ( "file_type_ini", 0xf15b ), ( "file_type_ink", 0xf15b ), ( "file_type_innosetup", 0xf15b ), ( "file_type_io", 0xf15b ), ( "file_type_iodine", 0xf15b ), ( "file_type_ionic", 0xf15b ), ( "file_type_jake", 0xf15b ), ( "file_type_janet", 0xf15b ), ( "file_type_jar", 0xe256 ), //  ( "file_type_jasmine", 0xf15b ), ( "file_type_java", 0xe256 ), //  ( "file_type_jbuilder", 0xe256 ), //  ( "file_type_jekyll", 0xe70d ), //  ( "file_type_jenkins", 0xe767 ), //  ( "file_type_jest", 0xf0668 ), // 󰙨 ( "file_type_jest_snapshot", 0xf0668 ), // 󰙨 ( "file_type_jinja", 0xe66f ), //  ( "file_type_jpm", 0xf15b ), ( "file_type_js", 0xf031e ), //  ( "file_type_js_official", 0xf031e ), //  ( "file_type_jsbeautify", 0xf031e ), //  ( "file_type_jsconfig", 0xf031e ), //  ( "file_type_jscpd", 0xf031e ), //  ( "file_type_jshint", 0xf031e ), //  ( "file_type_jsmap", 0xf031e ), //  ( "file_type_json", 0xe60b ), //  ( "file_type_json2", 0xe60b ), //  ( "file_type_json5", 0xe60b ), //  ( "file_type_json_official", 0xe60b ), //  ( "file_type_jsonld", 0xf0626 ), // 󰘦 ( "file_type_jsonnet", 0xf0626 ), // 󰘦 ( "file_type_jsp", 0xf15b ), ( "file_type_jss", 0xf15b ), ( "file_type_julia", 0xe624 ), //  ( "file_type_julia2", 0xe624 ), //  ( "file_type_jupyter", 0x10018B ), ( "file_type_karma", 0xf15b ), ( "file_type_key", 0xf1184 ), // 󱆄 ( "file_type_kitchenci", 0xf15b ), ( "file_type_kite", 0xf15b ), ( "file_type_kivy", 0xf15b ), ( "file_type_kos", 0xf15b ), ( "file_type_kotlin", 0xe634 ), //  ( "file_type_kusto", 0xf15b ), ( "file_type_latino", 0xf15b ), ( "file_type_layout", 0xf15b ), ( "file_type_lerna", 0xf15b ), ( "file_type_less", 0xe758 ), //  ( "file_type_lex", 0xf15b ), ( "file_type_license", 0xe60a ), //  ( "file_type_light_actionscript2", 0xf0477 ), // 󰑷 ( "file_type_light_ada", 0xf15b ), ( "file_type_light_apl", 0xf15b ), ( "file_type_light_babel", 0xf0a25 ), // 󰨥 ( "file_type_light_babel2", 0xf0a25 ), // 󰨥 ( "file_type_light_cabal", 0xf15b ), ( "file_type_light_circleci", 0xf15b ), ( "file_type_light_cloudfoundry", 0xf15b ), ( "file_type_light_codacy", 0xf15b ), ( "file_type_light_codeclimate", 0xf15b ), ( "file_type_light_config", 0xf107c ), // 󱁼 ( "file_type_light_crystal", 0xe62f ), //  ( "file_type_light_db", 0xe64d ), //  ( "file_type_light_docpad", 0xf1a9a ), // 󱪚 ( "file_type_light_drone", 0xf01e2 ), // 󰇢 ( "file_type_light_expo", 0xe27c ), //  ( "file_type_light_firebasehosting", 0xe787 ), //  ( "file_type_light_fla", 0xf15b ), ( "file_type_light_font", 0xf031 ), //  ( "file_type_light_gamemaker2", 0xf1393 ), // 󱎓 ( "file_type_light_gradle", 0xe738 ), //  ( "file_type_light_hjson", 0xe60b ), //  ( "file_type_light_ini", 0xf15b ), ( "file_type_light_io", 0xf15b ), ( "file_type_light_js", 0xf031e ), //  ( "file_type_light_jsconfig", 0xf031e ), //  ( "file_type_light_jsmap", 0xf031e ), //  ( "file_type_light_json", 0xe60b ), //  ( "file_type_light_json5", 0xe60b ), //  ( "file_type_light_jsonld", 0xf0626 ), // 󰘦 ( "file_type_light_kite", 0xf15b ), ( "file_type_light_lerna", 0xf15b ), ( "file_type_light_mdx", 0xe73e ), //  ( "file_type_light_mlang", 0xf15b ), ( "file_type_light_mustache", 0xe228 ), //  ( "file_type_light_next", 0xf15b ), ( "file_type_light_nim", 0xe677 ), //  ( "file_type_light_openHAB", 0xf15b ), ( "file_type_light_pcl", 0xf15b ), ( "file_type_light_pnpm", 0xe71e ), //  ( "file_type_light_prettier", 0xf15b ), ( "file_type_light_prisma", 0xe684 ), //  ( "file_type_light_purescript", 0xe630 ), //  ( "file_type_light_razzle", 0xf15b ), ( "file_type_light_rehype", 0xf15b ), ( "file_type_light_remark", 0xf15b ), ( "file_type_light_retext", 0xf15b ), ( "file_type_light_rubocop", 0xf15b ), ( "file_type_light_shaderlab", 0xf15b ), // 󰷋 ( "file_type_light_solidity", 0xf15a ), //  ( "file_type_light_stylelint", 0xe695 ), //  ( "file_type_light_stylus", 0xe759 ), //  ( "file_type_light_symfony", 0xe756 ), //  ( "file_type_light_systemd", 0xf15b ), ( "file_type_light_systemverilog", 0xf15b ), ( "file_type_light_testcafe", 0xf15b ), ( "file_type_light_testjs", 0xf0668 ), // 󰙨 ( "file_type_light_tex", 0xe69b ), //  ( "file_type_light_todo", 0xe69c ), //  ( "file_type_light_toml", 0xf0626 ), // 󰘦 ( "file_type_light_unibeautify", 0xf15b ), ( "file_type_light_vash", 0xf15b ), ( "file_type_light_vsix", 0xf15b ), ( "file_type_light_vsixmanifest", 0xf15b ), ( "file_type_light_xfl", 0xf15b ), ( "file_type_light_yaml", 0xf0626 ), // 󰘦 ( "file_type_light_zeit", 0xf15b ), ( "file_type_lighthouse", 0xf15b ), ( "file_type_lime", 0xf15b ), ( "file_type_lintstagedrc", 0xf15b ), ( "file_type_liquid", 0xf15b ), ( "file_type_lisp", 0xf15b ), ( "file_type_livescript", 0xf15b ), ( "file_type_lnk", 0xf15b ), ( "file_type_locale", 0xf1ab ), //  ( "file_type_log", 0xf1085 ), // 󱂅 ( "file_type_lolcode", 0xf15b ), ( "file_type_lsl", 0xf15b ), ( "file_type_lua", 0xe620 ), //  ( "file_type_lync", 0xf15b ), ( "file_type_makefile", 0xe673 ), //  ( "file_type_manifest", 0xf15b ), ( "file_type_manifest_bak", 0xf15b ), ( "file_type_manifest_skip", 0xf15b ), ( "file_type_map", 0xf15b ), ( "file_type_mariadb", 0xe64d ), //  ( "file_type_markdown", 0xf48a ), //  ( "file_type_markdownlint", 0xf48a ), //  ( "file_type_marko", 0xf15b ), ( "file_type_markojs", 0xf15b ), ( "file_type_matlab", 0xf15b ), ( "file_type_maven", 0xe674 ), //  ( "file_type_maxscript", 0xf15b ), ( "file_type_maya", 0xf15b ), ( "file_type_mdx", 0xe73e ), //  ( "file_type_mediawiki", 0xf15b ), ( "file_type_mercurial", 0xf15b ), ( "file_type_meson", 0xf15b ), ( "file_type_meteor", 0xf15b ), ( "file_type_mjml", 0xf15b ), ( "file_type_mlang", 0xf15b ), ( "file_type_mocha", 0xf15b ), ( "file_type_modernizr", 0xe720 ), //  ( "file_type_mojolicious", 0xf15b ), ( "file_type_moleculer", 0xf15b ), ( "file_type_mongo", 0xe7a4 ), //  ( "file_type_monotone", 0xf15b ), ( "file_type_mson", 0xf15b ), ( "file_type_mustache", 0xe228 ), //  ( "file_type_mysql", 0xe704 ), //  ( "file_type_nearly", 0xf15b ), ( "file_type_nest_adapter_js", 0xf031e ), ( "file_type_nest_adapter_ts", 0xf031e ), ( "file_type_nest_controller_js", 0xf031e ), ( "file_type_nest_controller_ts", 0xf031e ), ( "file_type_nest_decorator_js", 0xf031e ), ( "file_type_nest_decorator_ts", 0xf031e ), ( "file_type_nest_filter_js", 0xf031e ), ( "file_type_nest_filter_ts", 0xf031e ), ( "file_type_nest_gateway_js", 0xf031e ), ( "file_type_nest_gateway_ts", 0xf031e ), ( "file_type_nest_guard_js", 0xf031e ), ( "file_type_nest_guard_ts", 0xf031e ), ( "file_type_nest_interceptor_js", 0xf031e ), ( "file_type_nest_interceptor_ts", 0xf031e ), ( "file_type_nest_middleware_js", 0xf031e ), ( "file_type_nest_middleware_ts", 0xf031e ), ( "file_type_nest_module_js", 0xf031e ), ( "file_type_nest_module_ts", 0xf031e ), ( "file_type_nest_pipe_js", 0xf031e ), ( "file_type_nest_pipe_ts", 0xf031e ), ( "file_type_nest_service_js", 0xf031e ), ( "file_type_nest_service_ts", 0xf031e ), ( "file_type_nestjs", 0xf031e ), ( "file_type_netlify", 0xf15b ), ( "file_type_next", 0xf15b ), ( "file_type_ng_component_css", 0xe749 ), //  ( "file_type_ng_component_dart", 0xe64c ), //  ( "file_type_ng_component_html", 0xe736 ), //  ( "file_type_ng_component_js", 0xe781 ), //  ( "file_type_ng_component_js2", 0xe781 ), //  ( "file_type_ng_component_less", 0xe758 ), //  ( "file_type_ng_component_sass", 0xe74b ), //  ( "file_type_ng_component_scss", 0xe749 ), //  ( "file_type_ng_component_ts", 0xf06e6 ), // 󰛦 ( "file_type_ng_component_ts2", 0xf06e6 ), // 󰛦 ( "file_type_ng_controller_js", 0xe781 ), // .. ( "file_type_ng_controller_ts", 0xf06e6 ), ( "file_type_ng_directive_dart", 0xe64c ), ( "file_type_ng_directive_js", 0xe781 ), ( "file_type_ng_directive_js2", 0xe781 ), ( "file_type_ng_directive_ts", 0xf06e6 ), ( "file_type_ng_directive_ts2", 0xf06e6 ), ( "file_type_ng_guard_dart", 0xe64c ), ( "file_type_ng_guard_js", 0xe781 ), ( "file_type_ng_guard_ts", 0xf06e6 ), // .. ( "file_type_ng_interceptor_dart", 0xe64c ), ( "file_type_ng_interceptor_js", 0xe781 ), ( "file_type_ng_interceptor_ts", 0xf06e6 ), ( "file_type_ng_module_dart", 0xe64c ), ( "file_type_ng_module_js", 0xe781 ), ( "file_type_ng_module_js2", 0xe781 ), ( "file_type_ng_module_ts", 0xf06e6 ), ( "file_type_ng_module_ts2", 0xf06e6 ), ( "file_type_ng_pipe_dart", 0xe64c ), ( "file_type_ng_pipe_js", 0xe781 ), ( "file_type_ng_pipe_js2", 0xe781 ), ( "file_type_ng_pipe_ts", 0xf06e6 ), ( "file_type_ng_pipe_ts2", 0xf06e6 ), ( "file_type_ng_routing_dart", 0xe64c ), ( "file_type_ng_routing_js", 0xe781 ), ( "file_type_ng_routing_js2", 0xe781 ), ( "file_type_ng_routing_ts", 0xf06e6 ), ( "file_type_ng_routing_ts2", 0xe781 ), ( "file_type_ng_service_dart", 0xe64c ), ( "file_type_ng_service_js", 0xe781 ), ( "file_type_ng_service_js2", 0xe781 ), ( "file_type_ng_service_ts", 0xf06e6 ), ( "file_type_ng_service_ts2", 0xf06e6 ), ( "file_type_ng_smart_component_dart", 0xe64c ), ( "file_type_ng_smart_component_js", 0xe781 ), ( "file_type_ng_smart_component_js2", 0xe781 ), ( "file_type_ng_smart_component_ts", 0xf06e6 ), ( "file_type_ng_smart_component_ts2", 0xf06e6 ), ( "file_type_ng_tailwind", 0xf13ff ), // 󱏿 ( "file_type_nginx", 0xe776 ), //  ( "file_type_nim", 0xe677 ), //  ( "file_type_nimble", 0xf15b ), ( "file_type_ninja", 0xf0774 ), // 󰝴 ( "file_type_nix", 0xf1105 ), // 󱄅 ( "file_type_njsproj", 0xf15b ), ( "file_type_node", 0xf0399 ), // 󰎙 ( "file_type_node2", 0xf0399 ), // 󰎙 ( "file_type_nodemon", 0xf0399 ), // 󰎙 ( "file_type_npm", 0xe71e ), //  ( "file_type_nsi", 0xf15b ), ( "file_type_nsri", 0xf15b ), ( "file_type_nsri-integrity", 0xf15b ), ( "file_type_nuget", 0xf15b ), ( "file_type_numpy", 0xf15b ), ( "file_type_nunjucks", 0xe679 ), //  ( "file_type_nushell", 0xf07c6 ), // 󰟆 ( "file_type_nuxt", 0xf1106 ), // 󱄆 ( "file_type_nyc", 0xf15b ), ( "file_type_objectivec", 0xf0bf3 ), // 󰯳 ( "file_type_objectivecpp", 0xf0bf3 ), // 󰯳 ( "file_type_ocaml", 0xe67a ), //  ( "file_type_onenote", 0xf15b ), ( "file_type_openHAB", 0xf15b ), ( "file_type_opencl", 0xf15b ), ( "file_type_org", 0xe633 ), //  ( "file_type_outlook", 0xf0d22 ), // 󰴢 ( "file_type_ovpn", 0xf15b ), // ( "file_type_package", 0xeb29 ), //  ( "file_type_paket", 0xf03d7 ), // 󰏗 ( "file_type_patch", 0xf15b ), // ( "file_type_pcl", 0xf15b ), // ( "file_type_pddl", 0xf15b), ( "file_type_pddl_happenings", 0xf1b5 ), // ( "file_type_pddl_plan", 0xf15b ), ( "file_type_pdf", 0xeaeb ), //  ( "file_type_pdf2", 0xf15b ), ( "file_type_perl", 0xe769 ), //  ( "file_type_perl2", 0xe769 ), ( "file_type_perl6", 0xe769 ), ( "file_type_pgsql", 0xe76e ), //  ( "file_type_photoshop", 0xe7b8 ), //  ( "file_type_photoshop2", 0xe7b8 ), ( "file_type_php", 0xe73d ), //  ( "file_type_php2", 0xe608 ), //  ( "file_type_php3", 0xf031f ), // 󰌟 ( "file_type_phpcsfixer", 0xe73d ), ( "file_type_phpunit", 0xe73d ), ( "file_type_phraseapp", 0xf15b ), ( "file_type_pine", 0xf15b ), ( "file_type_pip", 0xe73c ), //  ( "file_type_plantuml", 0xf15b ), ( "file_type_platformio", 0xf15b ), ( "file_type_plsql", 0xf15b ), ( "file_type_plsql_package", 0xf15b ), ( "file_type_plsql_package_body", 0xf15b ), ( "file_type_plsql_package_header", 0xf15b ), ( "file_type_plsql_package_spec", 0xf15b ), ( "file_type_pnpm", 0xe71e ), //  ( "file_type_poedit", 0xf15b ), ( "file_type_polymer", 0xf0421 ), // 󰐡 ( "file_type_pony", 0xf15b ), ( "file_type_postcss", 0xf13c ), //  ( "file_type_postcssconfig", 0xf13c ), ( "file_type_powerpoint", 0xf0227 ), // 󰈧 ( "file_type_powerpoint2", 0xf0227 ), ( "file_type_powershell", 0xf0a0a ), ( "file_type_powershell2", 0xf0a0a ), ( "file_type_powershell_format", 0xf0a0a ), ( "file_type_powershell_psd", 0xf0a0a ), ( "file_type_powershell_psd2", 0xf0a0a ), ( "file_type_powershell_psm", 0xf0a0a ), ( "file_type_powershell_psm2", 0xf0a0a ), ( "file_type_powershell_types", 0xf0a0a ), // 󰨊 ( "file_type_precommit", 0xe729 ), //  ( "file_type_prettier", 0xf15b ), ( "file_type_prisma", 0xe684 ), //  ( "file_type_processinglang", 0xf15b ), ( "file_type_procfile", 0xf15b ), ( "file_type_progress", 0xf15b ), ( "file_type_prolog", 0xf15b ), ( "file_type_prometheus", 0xf15b ), ( "file_type_protobuf", 0xf15b ), ( "file_type_protractor", 0xf15b ), ( "file_type_publisher", 0xf15b ), ( "file_type_pug", 0xf15b ), ( "file_type_puppet", 0xe631 ), //  ( "file_type_purescript", 0xe630 ), //  ( "file_type_pyret", 0xf15b ), ( "file_type_python", 0xe73c ), //  ( "file_type_pyup", 0xe73c ), ( "file_type_q", 0xf15b ), ( "file_type_qbs", 0xf15b ), ( "file_type_qlikview", 0xf15b ), ( "file_type_qml", 0xf15b ), ( "file_type_qmldir", 0xf15b ), ( "file_type_qsharp", 0xf15b ), ( "file_type_quasar", 0xf15b ), ( "file_type_r", 0xf0b19 ), // 󰬙 ( "file_type_racket", 0xf15b ), ( "file_type_rails", 0xe604 ), //  ( "file_type_rake", 0xf15b ), ( "file_type_raml", 0xf0626 ), // 󰘦 ( "file_type_razor", 0xf1997 ), // 󱦗 ( "file_type_razzle", 0xf15b ), ( "file_type_reactjs", 0xe7ba ), //  ( "file_type_reacttemplate", 0xe7ba), //  ( "file_type_reactts", 0xe7ba ), //  ( "file_type_reason", 0xe687 ), //  ( "file_type_red", 0xf15b ), ( "file_type_registry", 0xf15b ), ( "file_type_rego", 0xf15b ), ( "file_type_rehype", 0xf15b ), ( "file_type_remark", 0xf15b ), ( "file_type_renovate", 0xf15b), ( "file_type_rescript", 0xf15b ), ( "file_type_rest", 0xf15b), ( "file_type_retext", 0xf15b), ( "file_type_rexx", 0xf15b ), ( "file_type_riot", 0xf15b ), ( "file_type_rmd", 0xf15b ), ( "file_type_robotframework", 0xf15b), ( "file_type_robots", 0xf06a9 ), // 󰚩 ( "file_type_rollup", 0xe689 ), //  ( "file_type_rproj", 0xf15b ), ( "file_type_rspec", 0xf15b ), ( "file_type_rubocop", 0xf15b ), ( "file_type_ruby", 0xf0d2d ), // 󰴭 ( "file_type_rust", 0xe7a8 ), //  ( "file_type_rust_toolchain", 0xe7a8 ), ( "file_type_sails", 0xf15b ), ( "file_type_saltstack", 0xf15b ), ( "file_type_san", 0xf15b ), ( "file_type_sas", 0xe74b ), ( "file_type_sass", 0xe74b ), //  ( "file_type_sbt", 0xe68d ), //  ( "file_type_scala", 0xe737 ), //  ( "file_type_scilab", 0xf15b ), ( "file_type_script", 0xf06e6 ), ( "file_type_scss", 0xf13c ), ( "file_type_scss2", 0xf13c ), ( "file_type_sdlang", 0xf15b), ( "file_type_sentry", 0xf15b ), ( "file_type_sequelize", 0xf15b ), ( "file_type_serverless", 0xf15b ), ( "file_type_shaderlab", 0xf15b ), ( "file_type_shell", 0xe691 ), //  ( "file_type_silverstripe", 0xf15b ), ( "file_type_sketch", 0xf15b ), ( "file_type_skipper", 0xf15b ), ( "file_type_slang", 0xf15b ), ( "file_type_slice", 0xf15b ), ( "file_type_slim", 0xf15b ), ( "file_type_sln", 0xf15b ), ( "file_type_sln2", 0xf15b ), ( "file_type_smarty", 0xf15b ), ( "file_type_snapcraft", 0xf15b ), ( "file_type_snort", 0xf15b ), ( "file_type_snyk", 0xf15b ), ( "file_type_solidarity", 0xf15b ), ( "file_type_solidity", 0xf15b ), ( "file_type_source", 0xf15b ), ( "file_type_spacengine", 0xf15b ), ( "file_type_sqf", 0xf15b ), ( "file_type_sql", 0xf0b86 ), // 󰮆 ( "file_type_sqlite", 0xe7c4 ), //  ( "file_type_squirrel", 0xf15b ), ( "file_type_sss", 0xf15b ), ( "file_type_stan", 0xf15b ), ( "file_type_stata", 0xf15b ), ( "file_type_stencil", 0xf15b ), ( "file_type_storyboard", 0xf15b ), ( "file_type_storybook", 0xf15b ), ( "file_type_stylable", 0xf15b ), ( "file_type_style", 0xf15b ), ( "file_type_styled", 0xf15b ), ( "file_type_stylelint", 0xf15b ), ( "file_type_stylish_haskell", 0xf15b ), ( "file_type_stylus", 0xf15b ), ( "file_type_subversion", 0xf15b ), ( "file_type_svelte", 0xe697 ), //  ( "file_type_svg", 0xe698 ), //  ( "file_type_swagger", 0xf15b ), ( "file_type_swift", 0xe755 ), //  ( "file_type_swig", 0xf15b ), ( "file_type_symfony", 0xe756 ), //  ( "file_type_systemd", 0xf15b ), ( "file_type_systemverilog", 0xf15b ), ( "file_type_t4tt", 0xf15b ), ( "file_type_tailwind", 0xf13ff ), // 󱏿 ( "file_type_tcl", 0xf15b ), ( "file_type_tera", 0xf15b ), ( "file_type_terraform", 0xf1062 ), // 󱁢 ( "file_type_test", 0xf0668 ), ( "file_type_testcafe", 0xf15b ), ( "file_type_testjs", 0xf0668 ), // 󰙨 ( "file_type_testts", 0xf0668 ), ( "file_type_tex", 0xe69b ), //  ( "file_type_text", 0xf09a8 ), // 󰦨 ( "file_type_textile", 0xf15b ), ( "file_type_tfs", 0xf15b ), ( "file_type_todo", 0xe69c ), //  ( "file_type_toml", 0xf0626 ), // 󰘦 ( "file_type_tox", 0xf15b ), ( "file_type_travis", 0xe77e ), //  ( "file_type_tsconfig", 0xe628 ), //  ( "file_type_tslint", 0xe628 ), //  ( "file_type_tt", 0xf15b ), ( "file_type_ttcn", 0xf15b ), ( "file_type_twig", 0xe61c ), //  ( "file_type_typescript", 0xf06e6 ), // 󰛦 ( "file_type_typescript_official", 0xf06e6 ), // 󰛦 ( "file_type_typescriptdef", 0xf06e6 ), // 󰛦 ( "file_type_typescriptdef_official", 0xf06e6 ), // 󰛦 ( "file_type_typo3", 0xe772 ), //  ( "file_type_unibeautify", 0xf15b ), ( "file_type_vagrant", 0xf15b ), ( "file_type_vala", 0xe69e ), //  ( "file_type_vapi", 0xf15b ), ( "file_type_vash", 0xf15b ), ( "file_type_vb", 0xf15b ), ( "file_type_vba", 0xf15b ), ( "file_type_vbhtml", 0xf15b ), ( "file_type_vbproj", 0xf15b ), ( "file_type_vcxproj", 0xf15b ), ( "file_type_velocity", 0xf15b ), ( "file_type_verilog", 0xf15b ), ( "file_type_version", 0xeb78 ), //  ( "file_type_vhdl", 0xf15b ), ( "file_type_video", 0xf022b ), // 󰈫 ( "file_type_view", 0xf15b ), ( "file_type_vim", 0xe62b ), //  ( "file_type_vlang", 0xe6ac ), //  ( "file_type_volt", 0xf15b ), ( "file_type_vscode", 0xe70c ), //  ( "file_type_vscode-insiders", 0xe70c ), //  ( "file_type_vscode2", 0xe70c ), //  ( "file_type_vscode3", 0xe70c ), //  ( "file_type_vsix", 0xf15b ), ( "file_type_vsixmanifest", 0xf15b ), ( "file_type_vue", 0xe6a0), //  ( "file_type_vueconfig", 0xe6a0 ), //  ( "file_type_wallaby", 0xf15b ), ( "file_type_wasm", 0xe6a1 ), //  ( "file_type_watchmanconfig", 0xf15b ), ( "file_type_webp", 0xf021f ), // 󰈟 ( "file_type_webpack", 0xf072b ), // 󰜫 ( "file_type_wenyan", 0xf15b ), ( "file_type_wercker", 0xf15b ), ( "file_type_wolfram", 0xf0a9a ), // 󰪚 ( "file_type_word", 0xe6a5 ), //  ( "file_type_word2", 0xe6a5 ), //  ( "file_type_wpml", 0xf15b ), ( "file_type_wurst", 0xf15b ), ( "file_type_wxml", 0xf15b ), ( "file_type_wxss", 0xf15b ), ( "file_type_xcode", 0xf15b ), ( "file_type_xfl", 0xf15b ), ( "file_type_xib", 0xf15b ), ( "file_type_xliff", 0xf15b ), ( "file_type_xmake", 0xf15b ), ( "file_type_xml", 0xf05c0 ), // 󰗀 ( "file_type_xquery", 0xf15b ), ( "file_type_xsl", 0xf15b ), ( "file_type_yacc", 0xf15b ), ( "file_type_yaml", 0xf0626 ), // 󰘦 ( "file_type_yamllint", 0xf0626 ), // 󰘦 ( "file_type_yandex", 0xf15b ), ( "file_type_yang", 0xf15b ), ( "file_type_yarn", 0xe6a7 ), //  ( "file_type_yeoman", 0xf15b ), ( "file_type_zeit", 0xf15b ), ( "file_type_zig", 0xe6a9 ), //  ( "file_type_zip", 0xf1c6 ), //  ( "file_type_zip2", 0xf1c6 ),//  ] broot-1.46.3/resources/icons/vscode/data/README000064400000000000000000000005661046102023000172540ustar 00000000000000Tips on editing these files in vi 1. Open ./icon_name_to_icon_code_point_map.rs then in the same session, switch to file you want to edit use C-n and C-y in edit mode 2. This plugin currently searches for lowercase, make everything so 3. Remember to run :Tabularize over ')' and ',' 4. :'<,'>!sort 5. `cargo run` in debug mode should figure out if some problems. broot-1.46.3/resources/icons/vscode/data/double_extension_to_icon_name_map.rs000064400000000000000000000005251046102023000256520ustar 00000000000000// SEE ./README on how to edit this file [ ( "gradle.kts" , "file_type_gradle" ), ( "tar.gz" , "file_type_zip" ), ( "tar.xz" , "file_type_zip" ), ( "tar.zst" , "file_type_zip" ), ] broot-1.46.3/resources/icons/vscode/data/extension_to_icon_name_map.rs000064400000000000000000001404711046102023000243250ustar 00000000000000// SEE ./README on how to edit this file [ ( "3g2" , "file_type_video" ) , ( "3gp" , "file_type_video" ) , ( "7z" , "file_type_zip2" ) , ( "aac" , "file_type_audio" ) , ( "accda" , "file_type_access2" ) , ( "accdb" , "file_type_access2" ) , ( "accdc" , "file_type_access2" ) , ( "accde" , "file_type_access2" ) , ( "accdp" , "file_type_access2" ) , ( "accdr" , "file_type_access2" ) , ( "accdt" , "file_type_access2" ) , ( "accdu" , "file_type_access2" ) , ( "act" , "file_type_audio" ) , ( "adapter.js" , "file_type_nest_adapter_js" ) , ( "adapter.ts" , "file_type_nest_adapter_ts" ) , ( "ade" , "file_type_access2" ) , ( "adp" , "file_type_access2" ) , ( "afdesign" , "file_type_affinitydesigner" ) , ( "affinitydesigner" , "file_type_affinitydesigner" ) , ( "affinityphoto" , "file_type_affinityphoto" ) , ( "affinitypublisher" , "file_type_affinitypublisher" ) , ( "a" , "file_type_binary" ) , ( "afphoto" , "file_type_affinityphoto" ) , ( "afpub" , "file_type_affinitypublisher" ) , ( "aiff" , "file_type_audio" ) , ( "ai" , "file_type_ai2" ) , ( "amr" , "file_type_audio" ) , ( "amv" , "file_type_video" ) , ( "angular-cli.json" , "file_type_angular" ) , ( "angular.json" , "file_type_angular" ) , ( "ape" , "file_type_audio" ) , ( "api-extractor-base.json" , "file_type_api_extractor" ) , ( "api-extractor.json" , "file_type_api_extractor" ) , ( "app.config.js" , "file_type_expo" ) , ( "app.config.json5" , "file_type_expo" ) , ( "app.config.json" , "file_type_expo" ) , ( "app" , "file_type_binary" ) , ( "app.json" , "file_type_expo" ) , ( "app-routing.module.dart" , "file_type_ng_routing_dart" ) , ( "app-routing.module.js" , "file_type_ng_routing_js2" ) , ( "app-routing.module.ts" , "file_type_ng_routing_ts2" ) , ( "appveyor.yml" , "file_type_appveyor" ) , ( "ascx" , "file_type_aspx" ) , ( "asf" , "file_type_video" ) , ( "aspx" , "file_type_aspx" ) , ( "au" , "file_type_audio" ) , ( "aurelia.json" , "file_type_aurelia" ) , ( "avif" , "file_type_avif" ) , ( "avi" , "file_type_video" ) , ( "awk" , "file_type_awk" ) , ( "azure-pipelines.yml" , "file_type_azurepipelines" ) , ( "babelignore" , "file_type_babel2" ) , ( "babelrc" , "file_type_babel2" ) , ( "bazel.bazelrc" , "file_type_bazel" ) , ( "bazel.rc" , "file_type_bazel" ) , ( "bazelrc" , "file_type_bazel" ) , ( "bb" , "file_type_blitzbasic" ) , ( "bcmx" , "file_type_outlook" ) , ( "berksfile" , "file_type_chef" ) , ( "berksfile.lock" , "file_type_chef" ) , ( "bin" , "file_type_binary" ) , ( "bitbucket-pipelines.yml" , "file_type_bitbucketpipeline" ) , ( "bithoundrc" , "file_type_bithound" ) , ( "bmp" , "file_type_image" ) , ( "boringignore" , "file_type_darcs" ) , ( "bower.json" , "file_type_bower" ) , ( "bowerrc" , "file_type_bower" ) , ( "browserslist" , "file_type_browserslist" ) , ( "browserslistrc" , "file_type_browserslist" ) , ( "buckconfig" , "file_type_buckbuild" ) , ( "BUILD.bazel" , "file_type_bazel" ) , ( "build.ninja" , "file_type_ninja" ) , ( "bz2" , "file_type_zip2" ) , ( "bz" , "file_type_zip2" ) , ( "bzip2" , "file_type_zip2" ) , ( "bzrignore" , "file_type_bazaar" ) , ( "cake" , "file_type_cake" ) , ( "cargo.lock" , "file_type_cargo" ) , ( "cargo.toml" , "file_type_cargo" ) , ( "cer" , "file_type_cert" ) , ( "cfignore" , "file_type_cloudfoundry" ) , ( "c" , "file_type_c" ) , ( "checkstyle.json" , "file_type_haxecheckstyle" ) , ( "chefignore" , "file_type_chef" ) , ( "circle.yml" , "file_type_circleci" ) , ( "cjm" , "file_type_clojure" ) , ( "class" , "file_type_class" ) , ( "cl" , "file_type_opencl" ) , ( "cljc" , "file_type_clojure" ) , ( "cljs" , "file_type_clojurescript" ) , ( "cma" , "file_type_binary" ) , ( "cmi" , "file_type_binary" ) , ( "cmo" , "file_type_binary" ) , ( "cmxa" , "file_type_binary" ) , ( "cmx" , "file_type_binary" ) , ( "codacy.yaml" , "file_type_codacy" ) , ( "codacy.yml" , "file_type_codacy" ) , ( "codeclimate.yml" , "file_type_codeclimate" ) , ( "codecov.yml" , "file_type_codecov" ) , ( "coffeelintignore" , "file_type_coffeelint" ) , ( "coffeelint.json" , "file_type_coffeelint" ) , ( "commitlint.config.js" , "file_type_commitlint" ) , ( "component.dart" , "file_type_ng_component_dart" ) , ( "component.js" , "file_type_ng_component_js2" ) , ( "component.ts" , "file_type_ng_component_ts2" ) , ( "composer.json" , "file_type_composer" ) , ( "composer.lock" , "file_type_composer" ) , ( "conanfile.py" , "file_type_conan" ) , ( "conanfile.txt" , "file_type_conan" ) , ( "condarc" , "file_type_conda" ) , ( "config.codekit2" , "file_type_codekit" ) , ( "config.codekit3" , "file_type_codekit" ) , ( "config.codekit" , "file_type_codekit" ) , ( "container.dart" , "file_type_ng_smart_component_dart" ) , ( "container.js" , "file_type_ng_smart_component_js2" ) , ( "container.ts" , "file_type_ng_smart_component_ts2" ) , ( "controller.js" , "file_type_nest_controller_js" ) , ( "controller.ts" , "file_type_nest_controller_ts" ) , ( "coveralls.yml" , "file_type_coveralls" ) , ( "crec" , "file_type_lync" ) , ( "crl" , "file_type_cert" ) , ( "crowdin.yml" , "file_type_crowdin" ) , ( "crt" , "file_type_cert" ) , ( "csproj" , "file_type_csproj" ) , ( "csr" , "file_type_cert" ) , ( "csscomb.json" , "file_type_csscomb" ) , ( "csslintrc" , "file_type_csslint" ) , ( "css.map" , "file_type_cssmap" ) , ( "csv" , "file_type_text" ) , ( "csx" , "file_type_csharp2" ) , ( "cvsignore" , "file_type_cvs" ) , ( "db3" , "file_type_sqlite" ) , ( "db" , "file_type_db" ) , ( "dct" , "file_type_audio" ) , ( "decorator.js" , "file_type_nest_decorator_js" ) , ( "decorator.ts" , "file_type_nest_decorator_ts" ) , ( "dependabot.yml" , "file_type_dependabot" ) , ( "dependencies.yml" , "file_type_dependencies" ) , ( "der" , "file_type_cert" ) , ( "devcontainer.json" , "file_type_devcontainer" ) , ( "dio" , "file_type_drawio" ) , ( "directive.dart" , "file_type_ng_directive_dart" ) , ( "directive.js" , "file_type_ng_directive_js2" ) , ( "directive.ts" , "file_type_ng_directive_ts2" ) , ( "divx" , "file_type_video" ) , ( "djt" , "file_type_django" ) , ( "dll" , "file_type_binary" ) , ( "doc" , "file_type_word2" ) , ( "docker-compose.test.yml" , "file_type_dockertest2" ) , ( "docm" , "file_type_word2" ) , ( "docx" , "file_type_word2" ) , ( "doczrc" , "file_type_docz" ) , ( "dojorc" , "file_type_dojo" ) , ( "dot" , "file_type_word2" ) , ( "dotm" , "file_type_word2" ) , ( "dotx" , "file_type_word2" ) , ( "drawio" , "file_type_drawio" ) , ( "drone.yml" , "file_type_drone" ) , ( "drone.yml.sig" , "file_type_drone" ) , ( "dss" , "file_type_audio" ) , ( "dta" , "file_type_stata" ) , ( "d.ts" , "file_type_typescriptdef_official" ) , ( "dvc" , "file_type_dvc" ) , ( "dvf" , "file_type_audio" ) , ( "eco" , "file_type_docpad" ) , ( "editorconfig" , "file_type_editorconfig" ) , ( "ejs" , "file_type_ejs" ) , ( "ejs.t" , "file_type_hygen" ) , ( "elc" , "file_type_emacs" ) , ( "elm" , "file_type_elm" ) , ( "el" , "file_type_emacs" ) , ( "elm-package.json" , "file_type_elm2" ) , ( "emakefile" , "file_type_erlang2" ) , ( "emakerfile" , "file_type_erlang2" ) , ( "ember-cli" , "file_type_ember" ) , ( "enc" , "file_type_license" ) , ( "ensime" , "file_type_ensime" ) , ( "eot" , "file_type_font" ) , ( "eps" , "file_type_eps" ) , ( "eskip" , "file_type_skipper" ) , ( "eslintcache" , "file_type_eslint2" ) , ( "eslintignore" , "file_type_eslint2" ) , ( "eslintrc" , "file_type_eslint2" ) , ( "exe" , "file_type_binary" ) , ( "exp" , "file_type_tcl" ) , ( "f4a" , "file_type_video" ) , ( "f4b" , "file_type_video" ) , ( "f4p" , "file_type_video" ) , ( "f4v" , "file_type_video" ) , ( "favicon.ico" , "file_type_favicon" ) , ( "fbx" , "file_type_fbx" ) , ( "fig" , "file_type_matlab" ) , ( "filter.js" , "file_type_nest_filter_js" ) , ( "filter.ts" , "file_type_nest_filter_ts" ) , ( "firebase.json" , "file_type_firebasehosting" ) , ( "firebaserc" , "file_type_firebase" ) , ( "firestore.indexes.json" , "file_type_firestore" ) , ( "firestore.rules" , "file_type_firestore" ) , ( "fish" , "file_type_shell" ) , ( "flac" , "file_type_audio" ) , ( "fla" , "file_type_fla" ) , ( "flooignore" , "file_type_floobits" ) , ( "flowconfig" , "file_type_flow" ) , ( "flutter-plugins" , "file_type_flutter" ) , ( "flv" , "file_type_video" ) , ( "fods" , "file_type_excel2" ) , ( "format.ps1xml" , "file_type_powershell_format" ) , ( "fossaignore" , "file_type_fossa" ) , ( "fs" , "file_type_fsharp" ) , ( "fsproj" , "file_type_fsproj" ) , ( "fuse.js" , "file_type_fusebox" ) , ( "gateway.js" , "file_type_nest_gateway_js" ) , ( "gateway.ts" , "file_type_nest_gateway_ts" ) , ( "gemfile" , "file_type_bundler" ) , ( "gemfile.lock" , "file_type_bundler" ) , ( "gif" , "file_type_image" ) , ( "gitattributes" , "file_type_git" ) , ( "gitconfig" , "file_type_git" ) , ( "gitignore" , "file_type_git" ) , ( "gitkeep" , "file_type_git" ) , ( "gitlab-ci.yml" , "file_type_gitlab" ) , ( "gitmodules" , "file_type_git" ) , ( "glide.yml" , "file_type_glide" ) , ( "gmx" , "file_type_gamemaker" ) , ( "go.mod" , "file_type_go_package" ) , ( "go.sum" , "file_type_go_package" ) , ( "gqlconfig" , "file_type_graphql" ) , ( "gradle" , "file_type_gradle2" ) , ( "graphqlconfig" , "file_type_graphql_config" ) , ( "greenkeeper.json" , "file_type_greenkeeper" ) , ( "gsm" , "file_type_audio" ) , ( "guard.dart" , "file_type_ng_guard_dart" ) , ( "guard.js" , "file_type_nest_guard_js" ) , ( "guard.ts" , "file_type_nest_guard_ts" ) , ( "gvimrc" , "file_type_vim" ) , ( "gz" , "file_type_zip2" ) , ( "haxelib.json" , "file_type_haxe" ) , ( "h" , "file_type_cheader" ) , ( "hgignore" , "file_type_mercurial" ) , ( "hl" , "file_type_binary" ) , ( "hpp" , "file_type_cppheader" ) , ( "hs" , "file_type_haskell" ) , ( "htmlhintrc" , "file_type_htmlhint" ) , ( "husky.config.js" , "file_type_husky" ) , ( "huskyrc" , "file_type_husky" ) , ( "hxp" , "file_type_lime" ) , ( "hxproj" , "file_type_haxedevelop" ) , ( "ibc" , "file_type_idrisbin" ) , ( "ico" , "file_type_image" ) , ( "idr" , "file_type_idris" ) , ( "ignore-glob" , "file_type_fossil" ) , ( "iklax" , "file_type_audio" ) , ( "ilk" , "file_type_binary" ) , ( "inc" , "file_type_inc" ) , ( "include" , "file_type_inc" ) , ( "include.xml" , "file_type_lime" ) , ( "infopathxml" , "file_type_infopath" ) , ( "ino" , "file_type_arduino" ) , ( "integrity.json" , "file_type_nsri-integrity" ) , ( "interceptor.dart" , "file_type_ng_interceptor_dart" ) , ( "interceptor.js" , "file_type_nest_interceptor_js" ) , ( "interceptor.ts" , "file_type_nest_interceptor_ts" ) , ( "ionic.config.json" , "file_type_ionic" ) , ( "ionic.project" , "file_type_ionic" ) , ( "ipkg" , "file_type_idrispkg" ) , ( "ipynb" , "file_type_jupyter" ) , ( "iuml" , "file_type_plantuml" ) , ( "ivs" , "file_type_audio" ) , ( "jade-lint.json" , "file_type_pug" ) , ( "jade-lintrc" , "file_type_pug" ) , ( "jakefile" , "file_type_jake" ) , ( "jakefile.js" , "file_type_jake" ) , ( "jar" , "file_type_jar" ) , ( "jasmine.json" , "file_type_jasmine" ) , ( "java" , "file_type_java" ) , ( "jbuilder" , "file_type_jbuilder" ) , ( "jest.config.json" , "file_type_jest" ) , ( "jest.json" , "file_type_jest" ) , ( "jestrc" , "file_type_jest" ) , ( "jestrc.js" , "file_type_jest" ) , ( "jestrc.json" , "file_type_jest" ) , ( "jpeg" , "file_type_image" ) , ( "jpg" , "file_type_image" ) , ( "jpmignore" , "file_type_jpm" ) , ( "jsbeautify" , "file_type_jsbeautify" ) , ( "jsbeautifyrc" , "file_type_jsbeautify" ) , ( "jsconfig.json" , "file_type_jsconfig" ) , ( "jscpd.json" , "file_type_jscpd" ) , ( "js.flow" , "file_type_flow" ) , ( "jshintignore" , "file_type_jshint" ) , ( "jshintrc" , "file_type_jshint" ) , ( "js.map" , "file_type_jsmap" ) , ( "json5" , "file_type_json5" ) , ( "json" , "file_type_json_official" ) , ( "json-ld" , "file_type_jsonld" ) , ( "jsonld" , "file_type_jsonld" ) , ( "jsp" , "file_type_jsp" ) , ( "jss" , "file_type_jss" ) , ( "js.snap" , "file_type_jest_snapshot" ) , ( "jsx.snap" , "file_type_jest_snapshot" ) , ( "kdl" , "file_type_config" ) , ( "key" , "file_type_key" ) , ( "kitchen.yml" , "file_type_kitchenci" ) , ( "kiteignore" , "file_type_kite" ) , ( "kit" , "file_type_codekit" ) , ( "laccdb" , "file_type_access2" ) , ( "layout.htm" , "file_type_layout" ) , ( "layout.html" , "file_type_layout" ) , ( "ldb" , "file_type_access2" ) , ( "lerna.json" , "file_type_lerna" ) , ( "lhs" , "file_type_haskell" ) , ( "lib" , "file_type_binary" ) , ( "licence" , "file_type_license" ) , ( "license" , "file_type_license" ) , ( "lidr" , "file_type_idris" ) , ( "lint-staged.config.js" , "file_type_lintstagedrc" ) , ( "lintstagedrc" , "file_type_lintstagedrc" ) , ( "liquid" , "file_type_liquid" ) , ( "lnk" , "file_type_lnk" ) , ( "lock" , "emoji_type_lock" ) , ( "log" , "file_type_log" ) , ( "ls" , "file_type_livescript" ) , ( "lucee" , "file_type_cf2" ) , ( "m2v" , "file_type_video" ) , ( "m4a" , "file_type_audio" ) , ( "m4b" , "file_type_audio" ) , ( "m4p" , "file_type_audio" ) , ( "m4v" , "file_type_video" ) , ( "mailmap" , "file_type_git" ) , ( "makefile" , "file_type_makefile" ) , ( "mam" , "file_type_access2" ) , ( "manifest.bak" , "file_type_manifest_bak" ) , ( "manifest" , "file_type_manifest" ) , ( "manifest.skip" , "file_type_manifest_skip" ) , ( "map" , "file_type_map" ) , ( "maq" , "file_type_access2" ) , ( "markdown" , "file_type_markdown" ) , ( "markdownlint.json" , "file_type_markdownlint" ) , ( "marko.js" , "file_type_markojs" ) , ( "master" , "file_type_layout" ) , ( "maven.config" , "file_type_maven" ) , ( "mdb" , "file_type_access2" ) , ( "md" , "file_type_markdown" ) , ( "mdown" , "file_type_markdown" ) , ( "mdw" , "file_type_access2" ) , ( "mdx" , "file_type_mdx" ) , ( "merlin" , "file_type_ocaml" ) , ( "metadata" , "file_type_flutter" ) , ( "mex" , "file_type_matlab" ) , ( "mexn" , "file_type_matlab" ) , ( "mexrs6" , "file_type_matlab" ) , ( "middleware.js" , "file_type_nest_middleware_js" ) , ( "middleware.ts" , "file_type_nest_middleware_ts" ) , ( "mk3d" , "file_type_video" ) , ( "mkv" , "file_type_video" ) , ( "mmf" , "file_type_audio" ) , ( "mn" , "file_type_matlab" ) , ( "mocha.opts" , "file_type_mocha" ) , ( "modernizr" , "file_type_modernizr" ) , ( "module.dart" , "file_type_ng_module_dart" ) , ( "module.js" , "file_type_nest_module_js" ) , ( "module.ts" , "file_type_nest_module_ts" ) , ( "mo" , "file_type_poedit" ) , ( "mogg" , "file_type_audio" ) , ( "mov" , "file_type_video" ) , ( "mp2" , "file_type_video" ) , ( "mp3" , "file_type_audio" ) , ( "mp4" , "file_type_video" ) , ( "mpc" , "file_type_audio" ) , ( "mpe" , "file_type_video" ) , ( "mpeg2" , "file_type_video" ) , ( "mpeg" , "file_type_video" ) , ( "mpg" , "file_type_video" ) , ( "mpv" , "file_type_video" ) , ( "msg" , "file_type_outlook" ) , ( "mst" , "file_type_mustache" ) , ( "msv" , "file_type_audio" ) , ( "mtn-ignore" , "file_type_monotone" ) , ( "mum" , "file_type_matlab" ) , ( "mustache" , "file_type_mustache" ) , ( "mx3" , "file_type_matlab" ) , ( "mx" , "file_type_matlab" ) , ( "ndll" , "file_type_binary" ) , ( "nest-cli.json" , "file_type_nestjs" ) , ( "nestconfig.json" , "file_type_nestjs" ) , ( "netlify.toml" , "file_type_netlify" ) , ( "next.config.js" , "file_type_next" ) , ( "n" , "file_type_binary" ) , ( "nginx.conf" , "file_type_nginx" ) , ( "ng-tailwind.js" , "file_type_ng_tailwind" ) , ( "nix" , "file_type_nix" ) , ( "njs" , "file_type_nunjucks" ) , ( "njsproj" , "file_type_njsproj" ) , ( "nodemon.json" , "file_type_nodemon" ) , ( "node-version" , "file_type_node2" ) , ( "nowignore" , "file_type_zeit" ) , ( "now.json" , "file_type_zeit" ) , ( "npmignore" , "file_type_npm" ) , ( "npmrc" , "file_type_npm" ) , ( "npm-shrinkwrap.json" , "file_type_npm" ) , ( "npy" , "file_type_numpy" ) , ( "npz" , "file_type_numpy" ) , ( "nsri.config.js" , "file_type_nsri" ) , ( "nsriignore" , "file_type_nsri" ) , ( "nsrirc" , "file_type_nsri" ) , ( "nsv" , "file_type_video" ) , ( "nu" , "file_type_nushell" ) , ( "nunj" , "file_type_nunjucks" ) , ( "nupkg" , "file_type_nuget" ) , ( "nuspec" , "file_type_nuget" ) , ( "nvmrc" , "file_type_node2" ) , ( "nycrc" , "file_type_nyc" ) , ( "nycrc.json" , "file_type_nyc" ) , ( "obj" , "file_type_binary" ) , ( "ocrec" , "file_type_lync" ) , ( "ods" , "file_type_excel2" ) , ( "o" , "file_type_binary" ) , ( "oft" , "file_type_outlook" ) , ( "oga" , "file_type_audio" ) , ( "ogg" , "file_type_audio" ) , ( "ogv" , "file_type_video" ) , ( "one" , "file_type_onenote" ) , ( "onepkg" , "file_type_onenote" ) , ( "onetoc2" , "file_type_onenote" ) , ( "onetoc" , "file_type_onenote" ) , ( "opencl" , "file_type_opencl" ) , ( "opus" , "file_type_audio" ) , ( "org" , "file_type_org" ) , ( "otf" , "file_type_font" ) , ( "otm" , "file_type_outlook" ) , ( "ovpn" , "file_type_ovpn" ) , ( "p12" , "file_type_cert" ) , ( "p4ignore" , "file_type_helix" ) , ( "p7b" , "file_type_cert" ) , ( "p7r" , "file_type_cert" ) , ( "package.json" , "file_type_npm" ) , ( "package-lock.json" , "file_type_npm" ) , ( "package.pins" , "file_type_swift" ) , ( "packages" , "file_type_flutter_package" ) , ( "pa" , "file_type_powerpoint2" ) , ( "page.dart" , "file_type_ng_smart_component_dart" ) , ( "page.js" , "file_type_ng_smart_component_js2" ) , ( "page.ts" , "file_type_ng_smart_component_ts2" ) , ( "patch" , "file_type_patch" ) , ( "pcd" , "file_type_pcl" ) , ( "pck" , "file_type_plsql_package" ) , ( "pdb" , "file_type_binary" ) , ( "pde" , "file_type_arduino" ) , ( "pdf" , "file_type_pdf2" ) , ( "pem" , "file_type_key" ) , ( "pex" , "file_type_xml" ) , ( "pfa" , "file_type_font" ) , ( "pfb" , "file_type_font" ) , ( "P" , "file_type_prolog" ) , ( "pfx" , "file_type_cert" ) , ( "phar" , "file_type_php3" ) , ( "php1" , "file_type_php3" ) , ( "php2" , "file_type_php3" ) , ( "php3" , "file_type_php3" ) , ( "php4" , "file_type_php3" ) , ( "php5" , "file_type_php3" ) , ( "php6" , "file_type_php3" ) , ( "php_cs.dist" , "file_type_phpcsfixer" ) , ( "php_cs" , "file_type_phpcsfixer" ) , ( "phpsa" , "file_type_php3" ) , ( "phps" , "file_type_php3" ) , ( "phpt" , "file_type_php3" ) , ( "phpunit" , "file_type_phpunit" ) , ( "phpunit.xml.dist" , "file_type_phpunit" ) , ( "phpunit.xml" , "file_type_phpunit" ) , ( "phraseapp.yml" , "file_type_phraseapp" ) , ( "phtml" , "file_type_php3" ) , ( "pipe.dart" , "file_type_ng_pipe_dart" ) , ( "pipe.js" , "file_type_nest_pipe_js" ) , ( "pipe.ts" , "file_type_nest_pipe_ts" ) , ( "pipfile" , "file_type_pip" ) , ( "pipfile.lock" , "file_type_pip" ) , ( "pkb" , "file_type_plsql_package_body" ) , ( "pkg" , "file_type_package" ) , ( "pkh" , "file_type_plsql_package_header" ) , ( "pks" , "file_type_plsql_package_spec" ) , ( "plantuml" , "file_type_plantuml" ) , ( "platformio.ini" , "file_type_platformio" ) , ( "plist" , "file_type_config" ) , ( "png" , "file_type_image" ) , ( "pnpmfile.js" , "file_type_pnpm" ) , ( "pnpm-lock.yaml" , "file_type_pnpm" ) , ( "pnpm-workspace.yaml" , "file_type_pnpm" ) , ( "po" , "file_type_poedit" ) , ( "policyfile" , "file_type_chef" ) , ( "postcss.config.js" , "file_type_postcssconfig" ) , ( "postcssrc" , "file_type_postcssconfig" ) , ( "postcssrc.js" , "file_type_postcssconfig" ) , ( "postcssrc.json" , "file_type_postcssconfig" ) , ( "postcssrc.yml" , "file_type_postcssconfig" ) , ( "pot" , "file_type_powerpoint2" ) , ( "potm" , "file_type_powerpoint2" ) , ( "potx" , "file_type_powerpoint2" ) , ( "ppa" , "file_type_powerpoint2" ) , ( "ppam" , "file_type_powerpoint2" ) , ( "pps" , "file_type_powerpoint2" ) , ( "ppsm" , "file_type_powerpoint2" ) , ( "ppsx" , "file_type_powerpoint2" ) , ( "ppt" , "file_type_powerpoint2" ) , ( "pptm" , "file_type_powerpoint2" ) , ( "pptx" , "file_type_powerpoint2" ) , ( "pre-commit-config.yaml" , "file_type_precommit" ) , ( "prettierignore" , "file_type_prettier" ) , ( "prettierrc" , "file_type_prettier" ) , ( "prisma" , "file_type_prisma" ) , ( "pro" , "file_type_prolog" ) , ( "procfile" , "file_type_procfile" ) , ( "psd" , "file_type_photoshop2" ) , ( "psd1" , "file_type_powershell_psd2" ) , ( "psm1" , "file_type_powershell_psm2" ) , ( "psmdcp" , "file_type_nuget" ) , ( "pst" , "file_type_outlook" ) , ( "pu" , "file_type_plantuml" ) , ( "pub" , "file_type_publisher" ) , ( "pubspec.lock" , "file_type_flutter_package" ) , ( "pubspec.yaml" , "file_type_flutter_package" ) , ( "pug-lintrc" , "file_type_pug" ) , ( "pug-lintrc.js" , "file_type_pug" ) , ( "pug-lintrc.json" , "file_type_pug" ) , ( "puml" , "file_type_plantuml" ) , ( "puz" , "file_type_publisher" ) , ( "pyc" , "file_type_binary" ) , ( "pyd" , "file_type_binary" ) , ( "py" , "file_type_python" ) , ( "pyo" , "file_type_binary" ) , ( "pyup" , "file_type_pyup" ) , ( "pyup.yml" , "file_type_pyup" ) , ( "qbs" , "file_type_qbs" ) , ( "q" , "file_type_q" ) , ( "qmldir" , "file_type_qmldir" ) , ( "qt" , "file_type_video" ) , ( "quasar.conf.js" , "file_type_quasar" ) , ( "qvd" , "file_type_qlikview" ) , ( "qvw" , "file_type_qlikview" ) , ( "ra" , "file_type_audio" ) , ( "rakefile" , "file_type_rake" ) , ( "rake" , "file_type_rake" ) , ( "rar" , "file_type_zip2" ) , ( "raw" , "file_type_audio" ) , ( "re" , "file_type_reason" ) , ( "reg" , "file_type_registry" ) , ( "rego" , "file_type_rego" ) , ( "rehypeignore" , "file_type_rehype" ) , ( "rehyperc" , "file_type_rehype" ) , ( "remarkignore" , "file_type_remark" ) , ( "remarkrc" , "file_type_remark" ) , ( "renovaterc" , "file_type_renovate" ) , ( "retextignore" , "file_type_retext" ) , ( "retextrc" , "file_type_retext" ) , ( "rm" , "file_type_video" ) , ( "rmvb" , "file_type_video" ) , ( "robots.txt" , "file_type_robots" ) , ( "routing.dart" , "file_type_ng_routing_dart" ) , ( "routing.js" , "file_type_ng_routing_js2" ) , ( "routing.ts" , "file_type_ng_routing_ts2" ) , ( "rproj" , "file_type_rproj" ) , ( "rs" , "file_type_rust" ) , ( "rspec" , "file_type_rspec" ) , ( "rt" , "file_type_reacttemplate" ) , ( "rubocop_todo.yml" , "file_type_rubocop" ) , ( "rubocop.yml" , "file_type_rubocop" ) , ( "rust-toolchain" , "file_type_rust_toolchain" ) , ( "rwd" , "file_type_matlab" ) , ( "sailsrc" , "file_type_sails" ) , ( "sass" , "file_type_sass" ) , ( "sbt" , "file_type_sbt" ) , ( "scala" , "file_type_scala" ) , ( "scpt" , "file_type_binary" ) , ( "scptd" , "file_type_binary" ) , ( "scssm" , "file_type_scss" ) , ( "sentryclirc" , "file_type_sentry" ) , ( "sequelizerc" , "file_type_sequelize" ) , ( "serverless.yml" , "file_type_serverless" ) , ( "service.dart" , "file_type_ng_service_dart" ) , ( "service.js" , "file_type_nest_service_js" ) , ( "service.ts" , "file_type_nest_service_ts" ) , ( "sfd" , "file_type_font" ) , ( "sh" , "file_type_shell" ) , ( "sig" , "file_type_onenote" ) , ( "sketch" , "file_type_sketch" ) , ( "slddc" , "file_type_matlab" ) , ( "sldm" , "file_type_powerpoint2" ) , ( "sldx" , "file_type_powerpoint2" ) , ( "sln" , "file_type_sln2" ) , ( "sls" , "file_type_saltstack" ) , ( "slx" , "file_type_matlab" ) , ( "smv" , "file_type_matlab" ) , ( "snapcraft.yaml" , "file_type_snapcraft" ) , ( "snyk" , "file_type_snyk" ) , ( "so" , "file_type_binary" ) , ( "solidarity" , "file_type_solidarity" ) , ( "solidarity.json" , "file_type_solidarity" ) , ( "spe" , "file_type_spacengine" ) , ( "sqlite3" , "file_type_sqlite" ) , ( "sqlite" , "file_type_sqlite" ) , ( "src" , "file_type_cert" ) , ( "sss" , "file_type_sss" ) , ( "sst" , "file_type_cert" ) , ( "stl" , "file_type_cert" ) , ( "storyboard" , "file_type_storyboard" ) , ( "stylelintcache" , "file_type_stylelint" ) , ( "stylelintignore" , "file_type_stylelint" ) , ( "stylelintrc" , "file_type_stylelint" ) , ( "stylish-haskell.yaml" , "file_type_stylish_haskell" ) , ( "svg" , "file_type_svg" ) , ( "svi" , "file_type_video" ) , ( "svnignore" , "file_type_subversion" ) , ( "swc" , "file_type_flash" ) , ( "swf" , "file_type_flash" ) , ( "symfony.lock" , "file_type_symfony" ) , ( "tar" , "file_type_zip2" ) , ( "tcl" , "file_type_tcl" ) , ( "testcaferc.json" , "file_type_testcafe" ) , ( "texi" , "file_type_tex" ) , ( "tf" , "file_type_terraform" ) , ( "tfignore" , "file_type_tfs" ) , ( "tfstate" , "file_type_terraform" ) , ( "tgz" , "file_type_zip2" ) , ( "tiff" , "file_type_image" ) , ( "tikz" , "file_type_tex" ) , ( "tlg" , "file_type_log" ) , ( "tmlanguage" , "file_type_xml" ) , ( "todo" , "file_type_todo" ) , ( "toml" , "file_type_toml" ) , ( "tox.ini" , "file_type_tox" ) , ( "travis.yml" , "file_type_travis" ) , ( "tslint.json" , "file_type_tslint" ) , ( "tslint.yaml" , "file_type_tslint" ) , ( "tslint.yml" , "file_type_tslint" ) , ( "ts.snap" , "file_type_jest_snapshot" ) , ( "tst" , "file_type_test" ) , ( "tsx.snap" , "file_type_jest_snapshot" ) , ( "tt2" , "file_type_tt" ) , ( "tta" , "file_type_audio" ) , ( "ttf" , "file_type_font" ) , ( "types.ps1xml" , "file_type_powershell_types" ) , ( "unibeautify.config.js" , "file_type_unibeautify" ) , ( "unibeautifyrc" , "file_type_unibeautify" ) , ( "unity" , "file_type_shaderlab" ) , ( "vagrantfile" , "file_type_vagrant" ) , ( "vala" , "file_type_vala" ) , ( "vapi" , "file_type_vapi" ) , ( "vash" , "file_type_vash" ) , ( "vbhtml" , "file_type_vbhtml" ) , ( "vbproj" , "file_type_vbproj" ) , ( "vcxproj" , "file_type_vcxproj" ) , ( "vercelignore" , "file_type_zeit" ) , ( "vercel.json" , "file_type_zeit" ) , ( "vimrc" , "file_type_vim" ) , ( "vob" , "file_type_video" ) , ( "vox" , "file_type_audio" ) , ( "vscodeignore" , "file_type_vscode-insiders" ) , ( "vsix" , "file_type_vsix" ) , ( "vsixmanifest" , "file_type_vsixmanifest" ) , ( "vsts-ci.yml" , "file_type_azurepipelines" ) , ( "vue.config.js" , "file_type_vueconfig" ) , ( "vuerc" , "file_type_vueconfig" ) , ( "wasm" , "file_type_wasm" ) , ( "watchmanconfig" , "file_type_watchmanconfig" ) , ( "wav" , "file_type_audio" ) , ( "webm" , "file_type_video" ) , ( "webp" , "file_type_webp" ) , ( "wercker.yml" , "file_type_wercker" ) , ( "wll" , "file_type_word2" ) , ( "wma" , "file_type_audio" ) , ( "wmv" , "file_type_video" ) , ( "woff2" , "file_type_font" ) , ( "woff" , "file_type_font" ) , ( "wpml-config.xml" , "file_type_wpml" ) , ( "wxml" , "file_type_wxml" ) , ( "wxss" , "file_type_wxss" ) , ( "xcodeproj" , "file_type_xcode" ) , ( "xfl" , "file_type_xfl" ) , ( "xib" , "file_type_xib" ) , ( "xlf" , "file_type_xliff" ) , ( "xliff" , "file_type_xliff" ) , ( "xls" , "file_type_excel2" ) , ( "xlsm" , "file_type_excel2" ) , ( "xlsx" , "file_type_excel2" ) , ( "xsf" , "file_type_infopath" ) , ( "xsn" , "file_type_infopath" ) , ( "xtp2" , "file_type_infopath" ) , ( "xvc" , "file_type_matlab" ) , ( "xz" , "file_type_zip2" ) , ( "yaml" , "file_type_yaml" ) , ( "yamllint" , "file_type_yamllint" ) , ( "yarnclean" , "file_type_yarn" ) , ( "yarnignore" , "file_type_yarn" ) , ( "yarn-integrity" , "file_type_yarn" ) , ( "yarn.lock" , "file_type_yarn" ) , ( "yarn-metadata.json" , "file_type_yarn" ) , ( "yarnrc" , "file_type_yarn" ) , ( "yaspeller.json" , "file_type_yandex" ) , ( "yaspellerrc" , "file_type_yandex" ) , ( "yml" , "file_type_yaml" ) , ( "yo-rc.json" , "file_type_yeoman" ) , ( "yy" , "file_type_gamemaker2" ) , ( "yyp" , "file_type_gamemaker2" ) , ( "zip" , "file_type_zip2" ) , ( "zipx" , "file_type_zip2" ) , ( "zst" , "file_type_zip2" ) , ] broot-1.46.3/resources/icons/vscode/data/file_name_to_icon_name_map.rs000064400000000000000000000012151046102023000242200ustar 00000000000000[ ( ".scalafix.conf" , "file_type_config" ), ( ".scalafmt.conf" , "file_type_config" ), ( "build.properties" , "file_type_config" ), ( "eslint.config.cjs" , "file_type_eslint" ), ( "eslint.config.js" , "file_type_eslint" ), ( "eslint.config.mjs" , "file_type_eslint" ), ( "license" , "file_type_license"), ( "package-lock.json" , "file_type_npm" ), ( "package.json" , "file_type_npm" ), ( "readme" , "file_type_text" ), ( "todo" , "file_type_todo" ), ] broot-1.46.3/resources/icons/vscode/data/icon_name_to_icon_code_point_map.rs000064400000000000000000000661521046102023000254470ustar 00000000000000[ ( "emoji_type_lock", 0x1F512 ), ( "emoji_type_link", 0x1F517 ), ( "default_file", 0x100000 ), ( "default_folder_opened", 0x100001 ), ( "default_folder", 0x100002 ), ( "default_root_folder_opened", 0x100003 ), ( "default_root_folder", 0x100004 ), ( "file_type_access2", 0x100064 ), ( "file_type_access", 0x100065 ), ( "file_type_actionscript2", 0x100066 ), ( "file_type_actionscript", 0x100067 ), ( "file_type_ada", 0x100068 ), ( "file_type_advpl", 0x100069 ), ( "file_type_affectscript", 0x10006A ), ( "file_type_affinitydesigner", 0x10006B ), ( "file_type_affinityphoto", 0x10006C ), ( "file_type_affinitypublisher", 0x10006D ), ( "file_type_ai2", 0x10006E ), ( "file_type_ai", 0x10006F ), ( "file_type_al", 0x100070 ), ( "file_type_angular", 0x100071 ), ( "file_type_ansible", 0x100072 ), ( "file_type_antlr", 0x100073 ), ( "file_type_anyscript", 0x100074 ), ( "file_type_apache", 0x100075 ), ( "file_type_apex", 0x100076 ), ( "file_type_apib2", 0x100077 ), ( "file_type_apib", 0x100078 ), ( "file_type_api_extractor", 0x100079 ), ( "file_type_apl", 0x10007A ), ( "file_type_applescript", 0x10007B ), ( "file_type_appveyor", 0x10007C ), ( "file_type_arduino", 0x10007D ), ( "file_type_asciidoc", 0x10007E ), ( "file_type_asp", 0x10007F ), ( "file_type_aspx", 0x100080 ), ( "file_type_assembly", 0x100081 ), ( "file_type_ats", 0x100082 ), ( "file_type_audio", 0x100083 ), ( "file_type_aurelia", 0x100084 ), ( "file_type_autohotkey", 0x100085 ), ( "file_type_autoit", 0x100086 ), ( "file_type_avif", 0x100087 ), ( "file_type_avro", 0x100088 ), ( "file_type_awk", 0x100089 ), ( "file_type_aws", 0x10008A ), ( "file_type_azurepipelines", 0x10008B ), ( "file_type_azure", 0x10008C ), ( "file_type_babel2", 0x10008D ), ( "file_type_babel", 0x10008E ), ( "file_type_ballerina", 0x10008F ), ( "file_type_bats", 0x100090 ), ( "file_type_bat", 0x100091 ), ( "file_type_bazaar", 0x100092 ), ( "file_type_bazel", 0x100093 ), ( "file_type_befunge", 0x100094 ), ( "file_type_biml", 0x100095 ), ( "file_type_binary", 0x100096 ), ( "file_type_bitbucketpipeline", 0x100097 ), ( "file_type_bithound", 0x100098 ), ( "file_type_blade", 0x100099 ), ( "file_type_blitzbasic", 0x10009A ), ( "file_type_bolt", 0x10009B ), ( "file_type_bosque", 0x10009C ), ( "file_type_bower2", 0x10009D ), ( "file_type_bower", 0x10009E ), ( "file_type_browserslist", 0x10009F ), ( "file_type_buckbuild", 0x1000A0 ), ( "file_type_bundler", 0x1000A1 ), ( "file_type_c2", 0x1000A2 ), ( "file_type_c3", 0x1000A3 ), ( "file_type_cabal", 0x1000A4 ), ( "file_type_caddy", 0x1000A5 ), ( "file_type_cakephp", 0x1000A6 ), ( "file_type_cake", 0x1000A7 ), ( "file_type_c_al", 0x1000A8 ), ( "file_type_capacitor", 0x1000A9 ), ( "file_type_cargo", 0x1000AA ), ( "file_type_cddl", 0x1000AB ), ( "file_type_cert", 0x1000AC ), ( "file_type_ceylon", 0x1000AD ), ( "file_type_cf2", 0x1000AE ), ( "file_type_cfc2", 0x1000AF ), ( "file_type_cfc", 0x1000B0 ), ( "file_type_cfm2", 0x1000B1 ), ( "file_type_cfm", 0x1000B2 ), ( "file_type_cf", 0x1000B3 ), ( "file_type_cheader", 0x1000B4 ), ( "file_type_chef_cookbook", 0x1000B5 ), ( "file_type_chef", 0x1000B6 ), ( "file_type_circleci", 0x1000B7 ), ( "file_type_class", 0x1000B8 ), ( "file_type_clojurescript", 0x1000B9 ), ( "file_type_clojure", 0x1000BA ), ( "file_type_cloudfoundry", 0x1000BB ), ( "file_type_cmake", 0x1000BC ), ( "file_type_cobol", 0x1000BD ), ( "file_type_codacy", 0x1000BE ), ( "file_type_codeclimate", 0x1000BF ), ( "file_type_codecov", 0x1000C0 ), ( "file_type_codekit", 0x1000C1 ), ( "file_type_coffeelint", 0x1000C2 ), ( "file_type_coffeescript", 0x1000C3 ), ( "file_type_commitlint", 0x1000C4 ), ( "file_type_compass", 0x1000C5 ), ( "file_type_composer", 0x1000C6 ), ( "file_type_conan", 0x1000C7 ), ( "file_type_conda", 0x1000C8 ), ( "file_type_config", 0x1000C9 ), ( "file_type_confluence", 0x1000CA ), ( "file_type_coveralls", 0x1000CB ), ( "file_type_cpp2", 0x1000CC ), ( "file_type_cpp3", 0x1000CD ), ( "file_type_cppheader", 0x1000CE ), ( "file_type_cpp", 0x1000CF ), ( "file_type_crowdin", 0x1000D0 ), ( "file_type_crystal", 0x1000D1 ), ( "file_type_csharp2", 0x1000D2 ), ( "file_type_csharp", 0x1000D3 ), ( "file_type_csproj", 0x1000D4 ), ( "file_type_csscomb", 0x1000D5 ), ( "file_type_csslint", 0x1000D6 ), ( "file_type_cssmap", 0x1000D7 ), ( "file_type_css", 0x1000D8 ), ( "file_type_c", 0x1000D9 ), ( "file_type_cucumber", 0x1000DA ), ( "file_type_cuda", 0x1000DB ), ( "file_type_cvs", 0x1000DC ), ( "file_type_cypress", 0x1000DD ), ( "file_type_cython", 0x1000DE ), ( "file_type_dal", 0x1000DF ), ( "file_type_darcs", 0x1000E0 ), ( "file_type_dartlang", 0x1000E1 ), ( "file_type_db", 0x1000E2 ), ( "file_type_delphi", 0x1000E3 ), ( "file_type_dependabot", 0x1000E4 ), ( "file_type_dependencies", 0x1000E5 ), ( "file_type_devcontainer", 0x1000E6 ), ( "file_type_diff", 0x1000E7 ), ( "file_type_django", 0x1000E8 ), ( "file_type_dlang", 0x1000E9 ), ( "file_type_docker2", 0x1000EA ), ( "file_type_docker", 0x1000EB ), ( "file_type_dockertest2", 0x1000EC ), ( "file_type_dockertest", 0x1000ED ), ( "file_type_docpad", 0x1000EE ), ( "file_type_docz", 0x1000EF ), ( "file_type_dojo", 0x1000F0 ), ( "file_type_dotjs", 0x1000F1 ), ( "file_type_doxygen", 0x1000F2 ), ( "file_type_drawio", 0x1000F3 ), ( "file_type_drone", 0x1000F4 ), ( "file_type_drools", 0x1000F5 ), ( "file_type_dustjs", 0x1000F6 ), ( "file_type_dvc", 0x1000F7 ), ( "file_type_dylan", 0x1000F8 ), ( "file_type_edge2", 0x1000F9 ), ( "file_type_edge", 0x1000FA ), ( "file_type_editorconfig", 0x1000FB ), ( "file_type_eex", 0x1000FC ), ( "file_type_ejs", 0x1000FD ), ( "file_type_elasticbeanstalk", 0x1000FE ), ( "file_type_elastic", 0x1000FF ), ( "file_type_elixir", 0x100100 ), ( "file_type_elm2", 0x100101 ), ( "file_type_elm", 0x100102 ), ( "file_type_emacs", 0x100103 ), ( "file_type_ember", 0x100104 ), ( "file_type_ensime", 0x100105 ), ( "file_type_eps", 0x100106 ), ( "file_type_erb", 0x100107 ), ( "file_type_erlang2", 0x100108 ), ( "file_type_erlang", 0x100109 ), ( "file_type_eslint2", 0x10010A ), ( "file_type_eslint", 0x10010B ), ( "file_type_excel2", 0x10010C ), ( "file_type_excel", 0x10010D ), ( "file_type_expo", 0x10010E ), ( "file_type_falcon", 0x10010F ), ( "file_type_favicon", 0x100110 ), ( "file_type_fbx", 0x100111 ), ( "file_type_firebasehosting", 0x100112 ), ( "file_type_firebase", 0x100113 ), ( "file_type_firestore", 0x100114 ), ( "file_type_flash", 0x100115 ), ( "file_type_fla", 0x100116 ), ( "file_type_floobits", 0x100117 ), ( "file_type_flow", 0x100118 ), ( "file_type_flutter_package", 0x100119 ), ( "file_type_flutter", 0x10011A ), ( "file_type_font", 0x10011B ), ( "file_type_fortran", 0x10011C ), ( "file_type_fossa", 0x10011D ), ( "file_type_fossil", 0x10011E ), ( "file_type_freemarker", 0x10011F ), ( "file_type_fsharp2", 0x100120 ), ( "file_type_fsharp", 0x100121 ), ( "file_type_fsproj", 0x100122 ), ( "file_type_fthtml", 0x100123 ), ( "file_type_fusebox", 0x100124 ), ( "file_type_galen2", 0x100125 ), ( "file_type_galen", 0x100126 ), ( "file_type_gamemaker2", 0x100127 ), ( "file_type_gamemaker81", 0x100128 ), ( "file_type_gamemaker", 0x100129 ), ( "file_type_gatsby", 0x10012A ), ( "file_type_gcode", 0x10012B ), ( "file_type_genstat", 0x10012C ), ( "file_type_git2", 0x10012D ), ( "file_type_gitlab", 0x10012E ), ( "file_type_git", 0x10012F ), ( "file_type_glide", 0x100130 ), ( "file_type_glsl", 0x100131 ), ( "file_type_glyphs", 0x100132 ), ( "file_type_gnuplot", 0x100133 ), ( "file_type_go_aqua", 0x100134 ), ( "file_type_go_black", 0x100135 ), ( "file_type_godot", 0x100136 ), ( "file_type_go_fuchsia", 0x100137 ), ( "file_type_go_gopher", 0x100138 ), ( "file_type_go_lightblue", 0x100139 ), ( "file_type_go_package", 0x10013A ), ( "file_type_go", 0x10013B ), ( "file_type_go_white", 0x10013C ), ( "file_type_go_yellow", 0x10013D ), ( "file_type_gradle2", 0x10013E ), ( "file_type_gradle", 0x10013F ), ( "file_type_graphql_config", 0x100140 ), ( "file_type_graphql", 0x100141 ), ( "file_type_graphviz", 0x100142 ), ( "file_type_greenkeeper", 0x100143 ), ( "file_type_gridsome", 0x100144 ), ( "file_type_groovy2", 0x100145 ), ( "file_type_groovy", 0x100146 ), ( "file_type_grunt", 0x100147 ), ( "file_type_gulp", 0x100148 ), ( "file_type_haml", 0x100149 ), ( "file_type_handlebars2", 0x10014A ), ( "file_type_handlebars", 0x10014B ), ( "file_type_harbour", 0x10014C ), ( "file_type_haskell2", 0x10014D ), ( "file_type_haskell", 0x10014E ), ( "file_type_haxecheckstyle", 0x10014F ), ( "file_type_haxedevelop", 0x100150 ), ( "file_type_haxe", 0x100151 ), ( "file_type_helix", 0x100152 ), ( "file_type_helm", 0x100153 ), ( "file_type_hjson", 0x100154 ), ( "file_type_hlsl", 0x100155 ), ( "file_type_homeassistant", 0x100156 ), ( "file_type_host", 0x100157 ), ( "file_type_htmlhint", 0x100158 ), ( "file_type_html", 0x100159 ), ( "file_type_http", 0x10015A ), ( "file_type_hunspell", 0x10015B ), ( "file_type_husky", 0x10015C ), ( "file_type_hygen", 0x10015D ), ( "file_type_hy", 0x10015E ), ( "file_type_icl", 0x10015F ), ( "file_type_idrisbin", 0x100160 ), ( "file_type_idrispkg", 0x100161 ), ( "file_type_idris", 0x100162 ), ( "file_type_image", 0x100163 ), ( "file_type_imba", 0x100164 ), ( "file_type_inc", 0x100165 ), ( "file_type_infopath", 0x100166 ), ( "file_type_informix", 0x100167 ), ( "file_type_ini", 0x100168 ), ( "file_type_ink", 0x100169 ), ( "file_type_innosetup", 0x10016A ), ( "file_type_iodine", 0x10016B ), ( "file_type_ionic", 0x10016C ), ( "file_type_io", 0x10016D ), ( "file_type_jake", 0x10016E ), ( "file_type_janet", 0x10016F ), ( "file_type_jar", 0x100170 ), ( "file_type_jasmine", 0x100171 ), ( "file_type_java", 0x100172 ), ( "file_type_jbuilder", 0x100173 ), ( "file_type_jekyll", 0x100174 ), ( "file_type_jenkins", 0x100175 ), ( "file_type_jest_snapshot", 0x100176 ), ( "file_type_jest", 0x100177 ), ( "file_type_jinja", 0x100178 ), ( "file_type_jpm", 0x100179 ), ( "file_type_jsbeautify", 0x10017A ), ( "file_type_jsconfig", 0x10017B ), ( "file_type_jscpd", 0x10017C ), ( "file_type_jshint", 0x10017D ), ( "file_type_jsmap", 0x10017E ), ( "file_type_js_official", 0x10017F ), ( "file_type_json2", 0x100180 ), ( "file_type_json5", 0x100181 ), ( "file_type_jsonld", 0x100182 ), ( "file_type_jsonnet", 0x100183 ), ( "file_type_json_official", 0x100184 ), ( "file_type_json", 0x100185 ), ( "file_type_jsp", 0x100186 ), ( "file_type_jss", 0x100187 ), ( "file_type_js", 0x100188 ), ( "file_type_julia2", 0x100189 ), ( "#file_type_julia", 0x10018A ), ( "file_type_jupyter", 0x10018B ), ( "file_type_karma", 0x10018C ), ( "file_type_key", 0x10018D ), ( "file_type_kitchenci", 0x10018E ), ( "file_type_kite", 0x10018F ), ( "file_type_kivy", 0x100190 ), ( "file_type_kos", 0x100191 ), ( "file_type_kotlin", 0x100192 ), ( "file_type_kusto", 0x100193 ), ( "file_type_latino", 0x100194 ), ( "file_type_layout", 0x100195 ), ( "file_type_lerna", 0x100196 ), ( "file_type_less", 0x100197 ), ( "file_type_lex", 0x100198 ), ( "file_type_license", 0x100199 ), ( "file_type_light_actionscript2", 0x10019A ), ( "file_type_light_ada", 0x10019B ), ( "file_type_light_apl", 0x10019C ), ( "file_type_light_babel2", 0x10019D ), ( "file_type_light_babel", 0x10019E ), ( "file_type_light_cabal", 0x10019F ), ( "file_type_light_circleci", 0x1001A0 ), ( "file_type_light_cloudfoundry", 0x1001A1 ), ( "file_type_light_codacy", 0x1001A2 ), ( "file_type_light_codeclimate", 0x1001A3 ), ( "file_type_light_config", 0x1001A4 ), ( "file_type_light_crystal", 0x1001A5 ), ( "file_type_light_db", 0x1001A6 ), ( "file_type_light_docpad", 0x1001A7 ), ( "file_type_light_drone", 0x1001A8 ), ( "file_type_light_expo", 0x1001A9 ), ( "file_type_light_firebasehosting", 0x1001AA ), ( "file_type_light_fla", 0x1001AB ), ( "file_type_light_font", 0x1001AC ), ( "file_type_light_gamemaker2", 0x1001AD ), ( "file_type_light_gradle", 0x1001AE ), ( "file_type_light_hjson", 0x1001AF ), ( "file_type_lighthouse", 0x1001B0 ), ( "file_type_light_ini", 0x1001B1 ), ( "file_type_light_io", 0x1001B2 ), ( "file_type_light_jsconfig", 0x1001B3 ), ( "file_type_light_jsmap", 0x1001B4 ), ( "file_type_light_json5", 0x1001B5 ), ( "file_type_light_jsonld", 0x1001B6 ), ( "file_type_light_json", 0x1001B7 ), ( "file_type_light_js", 0x1001B8 ), ( "file_type_light_kite", 0x1001B9 ), ( "file_type_light_lerna", 0x1001BA ), ( "file_type_light_mdx", 0x1001BB ), ( "file_type_light_mlang", 0x1001BC ), ( "file_type_light_mustache", 0x1001BD ), ( "file_type_light_next", 0x1001BE ), ( "file_type_light_nim", 0x1001BF ), ( "file_type_light_openHAB", 0x1001C0 ), ( "file_type_light_pcl", 0x1001C1 ), ( "file_type_light_pnpm", 0x1001C2 ), ( "file_type_light_prettier", 0x1001C3 ), ( "file_type_light_prisma", 0x1001C4 ), ( "file_type_light_purescript", 0x1001C5 ), ( "file_type_light_razzle", 0x1001C6 ), ( "file_type_light_rehype", 0x1001C7 ), ( "file_type_light_remark", 0x1001C8 ), ( "file_type_light_retext", 0x1001C9 ), ( "file_type_light_rubocop", 0x1001CA ), ( "file_type_light_shaderlab", 0x1001CB ), ( "file_type_light_solidity", 0x1001CC ), ( "file_type_light_stylelint", 0x1001CD ), ( "file_type_light_stylus", 0x1001CE ), ( "file_type_light_symfony", 0x1001CF ), ( "file_type_light_systemd", 0x1001D0 ), ( "file_type_light_systemverilog", 0x1001D1 ), ( "file_type_light_testcafe", 0x1001D2 ), ( "file_type_light_testjs", 0x1001D3 ), ( "file_type_light_tex", 0x1001D4 ), ( "file_type_light_todo", 0x1001D5 ), ( "file_type_light_toml", 0x1001D6 ), ( "file_type_light_unibeautify", 0x1001D7 ), ( "file_type_light_vash", 0x1001D8 ), ( "file_type_light_vsixmanifest", 0x1001D9 ), ( "file_type_light_vsix", 0x1001DA ), ( "file_type_light_xfl", 0x1001DB ), ( "file_type_light_yaml", 0x1001DC ), ( "file_type_light_zeit", 0x1001DD ), ( "file_type_lime", 0x1001DE ), ( "file_type_lintstagedrc", 0x1001DF ), ( "file_type_liquid", 0x1001E0 ), ( "file_type_lisp", 0x1001E1 ), ( "file_type_livescript", 0x1001E2 ), ( "file_type_lnk", 0x1001E3 ), ( "file_type_locale", 0x1001E4 ), ( "file_type_log", 0x1001E5 ), ( "file_type_lolcode", 0x1001E6 ), ( "file_type_lsl", 0x1001E7 ), ( "file_type_lua", 0x1001E8 ), ( "file_type_lync", 0x1001E9 ), ( "file_type_makefile", 0x1001EA ), ( "file_type_manifest_bak", 0x1001EB ), ( "file_type_manifest_skip", 0x1001EC ), ( "file_type_manifest", 0x1001ED ), ( "file_type_map", 0x1001EE ), ( "file_type_mariadb", 0x1001EF ), ( "file_type_markdownlint", 0x1001F0 ), ( "file_type_markdown", 0x1001F1 ), ( "file_type_markojs", 0x1001F2 ), ( "file_type_marko", 0x1001F3 ), ( "file_type_matlab", 0x1001F4 ), ( "file_type_maven", 0x1001F5 ), ( "file_type_maxscript", 0x1001F6 ), ( "file_type_maya", 0x1001F7 ), ( "file_type_mdx", 0x1001F8 ), ( "file_type_mediawiki", 0x1001F9 ), ( "file_type_mercurial", 0x1001FA ), ( "file_type_meson", 0x1001FB ), ( "file_type_meteor", 0x1001FC ), ( "file_type_mjml", 0x1001FD ), ( "file_type_mlang", 0x1001FE ), ( "file_type_mocha", 0x1001FF ), ( "file_type_modernizr", 0x100200 ), ( "file_type_mojolicious", 0x100201 ), ( "file_type_moleculer", 0x100202 ), ( "file_type_mongo", 0x100203 ), ( "file_type_monotone", 0x100204 ), ( "file_type_mson", 0x100205 ), ( "file_type_mustache", 0x100206 ), ( "file_type_mysql", 0x100207 ), ( "file_type_nearly", 0x100208 ), ( "file_type_nest_adapter_js", 0x100209 ), ( "file_type_nest_adapter_ts", 0x10020A ), ( "file_type_nest_controller_js", 0x10020B ), ( "file_type_nest_controller_ts", 0x10020C ), ( "file_type_nest_decorator_js", 0x10020D ), ( "file_type_nest_decorator_ts", 0x10020E ), ( "file_type_nest_filter_js", 0x10020F ), ( "file_type_nest_filter_ts", 0x100210 ), ( "file_type_nest_gateway_js", 0x100211 ), ( "file_type_nest_gateway_ts", 0x100212 ), ( "file_type_nest_guard_js", 0x100213 ), ( "file_type_nest_guard_ts", 0x100214 ), ( "file_type_nest_interceptor_js", 0x100215 ), ( "file_type_nest_interceptor_ts", 0x100216 ), ( "file_type_nestjs", 0x100217 ), ( "file_type_nest_middleware_js", 0x100218 ), ( "file_type_nest_middleware_ts", 0x100219 ), ( "file_type_nest_module_js", 0x10021A ), ( "file_type_nest_module_ts", 0x10021B ), ( "file_type_nest_pipe_js", 0x10021C ), ( "file_type_nest_pipe_ts", 0x10021D ), ( "file_type_nest_service_js", 0x10021E ), ( "file_type_nest_service_ts", 0x10021F ), ( "file_type_netlify", 0x100220 ), ( "file_type_next", 0x100221 ), ( "file_type_ng_component_css", 0x100222 ), ( "file_type_ng_component_dart", 0x100223 ), ( "file_type_ng_component_html", 0x100224 ), ( "file_type_ng_component_js2", 0x100225 ), ( "file_type_ng_component_js", 0x100226 ), ( "file_type_ng_component_less", 0x100227 ), ( "file_type_ng_component_sass", 0x100228 ), ( "file_type_ng_component_scss", 0x100229 ), ( "file_type_ng_component_ts2", 0x10022A ), ( "file_type_ng_component_ts", 0x10022B ), ( "file_type_ng_controller_js", 0x10022C ), ( "file_type_ng_controller_ts", 0x10022D ), ( "file_type_ng_directive_dart", 0x10022E ), ( "file_type_ng_directive_js2", 0x10022F ), ( "file_type_ng_directive_js", 0x100230 ), ( "file_type_ng_directive_ts2", 0x100231 ), ( "file_type_ng_directive_ts", 0x100232 ), ( "file_type_ng_guard_dart", 0x100233 ), ( "file_type_ng_guard_js", 0x100234 ), ( "file_type_ng_guard_ts", 0x100235 ), ( "file_type_ng_interceptor_dart", 0x100236 ), ( "file_type_ng_interceptor_js", 0x100237 ), ( "file_type_ng_interceptor_ts", 0x100238 ), ( "file_type_nginx", 0x100239 ), ( "file_type_ng_module_dart", 0x10023A ), ( "file_type_ng_module_js2", 0x10023B ), ( "file_type_ng_module_js", 0x10023C ), ( "file_type_ng_module_ts2", 0x10023D ), ( "file_type_ng_module_ts", 0x10023E ), ( "file_type_ng_pipe_dart", 0x10023F ), ( "file_type_ng_pipe_js2", 0x100240 ), ( "file_type_ng_pipe_js", 0x100241 ), ( "file_type_ng_pipe_ts2", 0x100242 ), ( "file_type_ng_pipe_ts", 0x100243 ), ( "file_type_ng_routing_dart", 0x100244 ), ( "file_type_ng_routing_js2", 0x100245 ), ( "file_type_ng_routing_js", 0x100246 ), ( "file_type_ng_routing_ts2", 0x100247 ), ( "file_type_ng_routing_ts", 0x100248 ), ( "file_type_ng_service_dart", 0x100249 ), ( "file_type_ng_service_js2", 0x10024A ), ( "file_type_ng_service_js", 0x10024B ), ( "file_type_ng_service_ts2", 0x10024C ), ( "file_type_ng_service_ts", 0x10024D ), ( "file_type_ng_smart_component_dart", 0x10024E ), ( "file_type_ng_smart_component_js2", 0x10024F ), ( "file_type_ng_smart_component_js", 0x100250 ), ( "file_type_ng_smart_component_ts2", 0x100251 ), ( "file_type_ng_smart_component_ts", 0x100252 ), ( "file_type_ng_tailwind", 0x100253 ), ( "file_type_nimble", 0x100254 ), ( "file_type_nim", 0x100255 ), ( "file_type_ninja", 0x100256 ), ( "file_type_nix", 0x100257 ), ( "file_type_njsproj", 0x100258 ), ( "file_type_node2", 0x100259 ), ( "file_type_nodemon", 0x10025A ), ( "file_type_node", 0x10025B ), ( "file_type_npm", 0x10025C ), ( "file_type_nsi", 0x10025D ), ( "file_type_nsri-integrity", 0x10025E ), ( "file_type_nsri", 0x10025F ), ( "file_type_nuget", 0x100260 ), ( "file_type_numpy", 0x100261 ), ( "file_type_nunjucks", 0x100262 ), ( "file_type_nushell", 0xf07c6 ), ( "file_type_nuxt", 0x100263 ), ( "file_type_nyc", 0x100264 ), ( "file_type_objectivecpp", 0x100265 ), ( "file_type_objectivec", 0x100266 ), ( "file_type_ocaml", 0x100267 ), ( "file_type_onenote", 0x100268 ), ( "file_type_opencl", 0x100269 ), ( "file_type_openHAB", 0x10026A ), ( "file_type_org", 0x10026B ), ( "file_type_outlook", 0x10026C ), ( "file_type_ovpn", 0x10026D ), ( "file_type_package", 0x10026E ), ( "file_type_paket", 0x10026F ), ( "file_type_patch", 0x100270 ), ( "file_type_pcl", 0x100271 ), ( "file_type_pddl_happenings", 0x100272 ), ( "file_type_pddl_plan", 0x100273 ), ( "file_type_pddl", 0x100274 ), ( "file_type_pdf2", 0x100275 ), ( "file_type_pdf", 0x100276 ), ( "file_type_perl2", 0x100277 ), ( "file_type_perl6", 0x100278 ), ( "file_type_perl", 0x100279 ), ( "file_type_pgsql", 0x10027A ), ( "file_type_photoshop2", 0x10027B ), ( "file_type_photoshop", 0x10027C ), ( "file_type_php2", 0x10027D ), ( "file_type_php3", 0x10027E ), ( "file_type_phpcsfixer", 0x10027F ), ( "file_type_php", 0x100280 ), ( "file_type_phpunit", 0x100281 ), ( "file_type_phraseapp", 0x100282 ), ( "file_type_pine", 0x100283 ), ( "file_type_pip", 0x100284 ), ( "file_type_plantuml", 0x100285 ), ( "file_type_platformio", 0x100286 ), ( "file_type_plsql_package_body", 0x100287 ), ( "file_type_plsql_package_header", 0x100288 ), ( "file_type_plsql_package_spec", 0x100289 ), ( "file_type_plsql_package", 0x10028A ), ( "file_type_plsql", 0x10028B ), ( "file_type_pnpm", 0x10028C ), ( "file_type_poedit", 0x10028D ), ( "file_type_polymer", 0x10028E ), ( "file_type_pony", 0x10028F ), ( "file_type_postcssconfig", 0x100290 ), ( "file_type_postcss", 0x100291 ), ( "file_type_powerpoint2", 0x100292 ), ( "file_type_powerpoint", 0x100293 ), ( "file_type_powershell2", 0x100294 ), ( "file_type_powershell_format", 0x100295 ), ( "file_type_powershell_psd2", 0x100296 ), ( "file_type_powershell_psd", 0x100297 ), ( "file_type_powershell_psm2", 0x100298 ), ( "file_type_powershell_psm", 0x100299 ), ( "file_type_powershell", 0x10029A ), ( "file_type_powershell_types", 0x10029B ), ( "file_type_precommit", 0x10029C ), ( "file_type_prettier", 0x10029D ), ( "file_type_prisma", 0x10029E ), ( "file_type_processinglang", 0x10029F ), ( "file_type_procfile", 0x1002A0 ), ( "file_type_progress", 0x1002A1 ), ( "file_type_prolog", 0x1002A2 ), ( "file_type_prometheus", 0x1002A3 ), ( "file_type_protobuf", 0x1002A4 ), ( "file_type_protractor", 0x1002A5 ), ( "file_type_publisher", 0x1002A6 ), ( "file_type_pug", 0x1002A7 ), ( "file_type_puppet", 0x1002A8 ), ( "file_type_purescript", 0x1002A9 ), ( "file_type_pyret", 0x1002AA ), ( "file_type_python", 0x1002AB ), ( "file_type_pyup", 0x1002AC ), ( "file_type_qbs", 0x1002AD ), ( "file_type_qlikview", 0x1002AE ), ( "file_type_qmldir", 0x1002AF ), ( "file_type_qml", 0x1002B0 ), ( "file_type_qsharp", 0x1002B1 ), ( "file_type_q", 0x1002B2 ), ( "file_type_quasar", 0x1002B3 ), ( "file_type_racket", 0x1002B4 ), ( "file_type_rails", 0x1002B5 ), ( "file_type_rake", 0x1002B6 ), ( "file_type_raml", 0x1002B7 ), ( "file_type_razor", 0x1002B8 ), ( "file_type_razzle", 0x1002B9 ), ( "file_type_reactjs", 0x1002BA ), ( "file_type_reacttemplate", 0x1002BB ), ( "file_type_reactts", 0x1002BC ), ( "file_type_reason", 0x1002BD ), ( "file_type_red", 0x1002BE ), ( "file_type_registry", 0x1002BF ), ( "file_type_rego", 0x1002C0 ), ( "file_type_rehype", 0x1002C1 ), ( "file_type_remark", 0x1002C2 ), ( "file_type_renovate", 0x1002C3 ), ( "file_type_rescript", 0x1002C4 ), ( "file_type_rest", 0x1002C5 ), ( "file_type_retext", 0x1002C6 ), ( "file_type_rexx", 0x1002C7 ), ( "file_type_riot", 0x1002C8 ), ( "file_type_rmd", 0x1002C9 ), ( "file_type_robotframework", 0x1002CA ), ( "file_type_robots", 0x1002CB ), ( "file_type_rollup", 0x1002CC ), ( "file_type_rproj", 0x1002CD ), ( "file_type_rspec", 0x1002CE ), ( "file_type_r", 0x1002CF ), ( "file_type_rubocop", 0x1002D0 ), ( "file_type_ruby", 0x1002D1 ), ( "file_type_rust", 0x1002D2 ), ( "file_type_rust_toolchain", 0x1002D3 ), ( "file_type_sails", 0x1002D4 ), ( "file_type_saltstack", 0x1002D5 ), ( "file_type_san", 0x1002D6 ), ( "file_type_sass", 0x1002D7 ), ( "file_type_sas", 0x1002D8 ), ( "file_type_sbt", 0x1002D9 ), ( "file_type_scala", 0x1002DA ), ( "file_type_scilab", 0x1002DB ), ( "file_type_script", 0x1002DC ), ( "file_type_scss2", 0x1002DD ), ( "file_type_scss", 0x1002DE ), ( "file_type_sdlang", 0x1002DF ), ( "file_type_sentry", 0x1002E0 ), ( "file_type_sequelize", 0x1002E1 ), ( "file_type_serverless", 0x1002E2 ), ( "file_type_shaderlab", 0x1002E3 ), ( "file_type_shell", 0x1002E4 ), ( "file_type_silverstripe", 0x1002E5 ), ( "file_type_sketch", 0x1002E6 ), ( "file_type_skipper", 0x1002E7 ), ( "file_type_slang", 0x1002E8 ), ( "file_type_slice", 0x1002E9 ), ( "file_type_slim", 0x1002EA ), ( "file_type_sln2", 0x1002EB ), ( "file_type_sln", 0x1002EC ), ( "file_type_smarty", 0x1002ED ), ( "file_type_snapcraft", 0x1002EE ), ( "file_type_snort", 0x1002EF ), ( "file_type_snyk", 0x1002F0 ), ( "file_type_solidarity", 0x1002F1 ), ( "file_type_solidity", 0x1002F2 ), ( "file_type_source", 0x1002F3 ), ( "file_type_spacengine", 0x1002F4 ), ( "file_type_sqf", 0x1002F5 ), ( "file_type_sqlite", 0x1002F6 ), ( "file_type_sql", 0x1002F7 ), ( "file_type_squirrel", 0x1002F8 ), ( "file_type_sss", 0x1002F9 ), ( "file_type_stan", 0x1002FA ), ( "file_type_stata", 0x1002FB ), ( "file_type_stencil", 0x1002FC ), ( "file_type_storyboard", 0x1002FD ), ( "file_type_storybook", 0x1002FE ), ( "file_type_stylable", 0x1002FF ), ( "file_type_styled", 0x100300 ), ( "file_type_stylelint", 0x100301 ), ( "file_type_style", 0x100302 ), ( "file_type_stylish_haskell", 0x100303 ), ( "file_type_stylus", 0x100304 ), ( "file_type_subversion", 0x100305 ), ( "file_type_svelte", 0x100306 ), ( "file_type_svg", 0x100307 ), ( "file_type_swagger", 0x100308 ), ( "file_type_swift", 0x100309 ), ( "file_type_swig", 0x10030A ), ( "file_type_symfony", 0x10030B ), ( "file_type_systemd", 0x10030C ), ( "file_type_systemverilog", 0x10030D ), ( "file_type_t4tt", 0x10030E ), ( "file_type_tailwind", 0x10030F ), ( "file_type_tcl", 0x100310 ), ( "file_type_tera", 0x100311 ), ( "file_type_terraform", 0x100312 ), ( "file_type_testcafe", 0x100313 ), ( "file_type_testjs", 0x100314 ), ( "file_type_test", 0x100315 ), ( "file_type_testts", 0x100316 ), ( "file_type_tex", 0x100317 ), ( "file_type_textile", 0x100318 ), ( "file_type_text", 0x100319 ), ( "file_type_tfs", 0x10031A ), ( "file_type_todo", 0x10031B ), ( "file_type_toml", 0x10031C ), ( "file_type_tox", 0x10031D ), ( "file_type_travis", 0x10031E ), ( "file_type_tsconfig", 0x10031F ), ( "file_type_tslint", 0x100320 ), ( "file_type_ttcn", 0x100321 ), ( "file_type_tt", 0x100322 ), ( "file_type_twig", 0x100323 ), ( "file_type_typescriptdef_official", 0x100324 ), ( "file_type_typescriptdef", 0x100325 ), ( "file_type_typescript_official", 0x100326 ), ( "file_type_typescript", 0x100327 ), ( "file_type_typo3", 0x100328 ), ( "file_type_unibeautify", 0x100329 ), ( "file_type_vagrant", 0x10032A ), ( "file_type_vala", 0x10032B ), ( "file_type_vapi", 0x10032C ), ( "file_type_vash", 0x10032D ), ( "file_type_vba", 0x10032E ), ( "file_type_vbhtml", 0x10032F ), ( "file_type_vbproj", 0x100330 ), ( "file_type_vb", 0x100331 ), ( "file_type_vcxproj", 0x100332 ), ( "file_type_velocity", 0x100333 ), ( "file_type_verilog", 0x100334 ), ( "file_type_vhdl", 0x100335 ), ( "file_type_video", 0x100336 ), ( "file_type_view", 0x100337 ), ( "file_type_vim", 0x100338 ), ( "file_type_vlang", 0x100339 ), ( "file_type_volt", 0x10033A ), ( "file_type_vscode2", 0x10033B ), ( "file_type_vscode3", 0x10033C ), ( "file_type_vscode-insiders", 0x10033D ), ( "file_type_vscode", 0x10033E ), ( "file_type_vsixmanifest", 0x10033F ), ( "file_type_vsix", 0x100340 ), ( "file_type_vueconfig", 0x100341 ), ( "file_type_vue", 0x100342 ), ( "file_type_wallaby", 0x100343 ), ( "file_type_wasm", 0x100344 ), ( "file_type_watchmanconfig", 0x100345 ), ( "file_type_webpack", 0x100346 ), ( "file_type_webp", 0x100347 ), ( "file_type_wenyan", 0x100348 ), ( "file_type_wercker", 0x100349 ), ( "file_type_wolfram", 0x10034A ), ( "file_type_word2", 0x10034B ), ( "file_type_word", 0x10034C ), ( "file_type_wpml", 0x10034D ), ( "file_type_wurst", 0x10034E ), ( "file_type_wxml", 0x10034F ), ( "file_type_wxss", 0x100350 ), ( "file_type_xcode", 0x100351 ), ( "file_type_xfl", 0x100352 ), ( "file_type_xib", 0x100353 ), ( "file_type_xliff", 0x100354 ), ( "file_type_xmake", 0x100355 ), ( "file_type_xml", 0x100356 ), ( "file_type_xquery", 0x100357 ), ( "file_type_xsl", 0x100358 ), ( "file_type_yacc", 0x100359 ), ( "file_type_yamllint", 0x10035A ), ( "file_type_yaml", 0x10035B ), ( "file_type_yandex", 0x10035C ), ( "file_type_yang", 0x10035D ), ( "file_type_yarn", 0x10035E ), ( "file_type_yeoman", 0x10035F ), ( "file_type_zeit", 0x100360 ), ( "file_type_zig", 0x100361 ), ( "file_type_zip2", 0x100362 ), ( "file_type_zip", 0x100363 ), ] broot-1.46.3/resources/icons/vscode/vscode.ttf000064400000000000000000024071501046102023000174670ustar 00000000000000 @OS/2_ni,H`cmap cvt !y Pgasp `glyf+%l head6hhea $hmtx{gfNlocaj T maxp@( name*(H 0Lpostq | !t_< Vg3 u Zl u@U@.1PfEd 8Z  l!>>F>s>b>>>H>>\<>>><<]>=0>>>>>>>>I>}>-B@>><>>6>f>}>>>>>M[]B>>q>>g>>HH>H>>>Ea>A?>>e>>>>3TwD>>>]J>>>]>D>>>rw>>E>>A;>]e>>>>>><>>D>>>>>>M8>>>>F>sH>>>J>>>q>>>F>5>>~t=>>D>a>9>@>>}>>>>>>>>>R@>ln,>>>>.>>>>>>>>O=>>)>rSR>=>>;>>D>>>>>>H>>eaaD>>y>>>>^>>>>}>>y><]>>>;">>:TFs>@BE>>D><>qF>>/=>D>y>>>}y>>>>>>>>}>}I>;>>>>?>>>m>>F>>>>>>|>N>>>=>>>>>>>>>>>>>>r>>=,=>>@>???????????????????????>>UUUPUUUUQU22UPUQUUUUUUUtUPUQUUPUQUUPUQUUPUQUUPIQI\>>>D>sp>>>]>A>??>>>>`CS>6>dNFh>FLEF>=>>>>><>}///>>=>>>>>>>>>>>}Z/|>><=>}>>>>>>>H~m>>>>>>}>>G>>]>u>>>>>;>>;>>>>>>>y>INA>M>_H>>>>l?>>>>>|_<>r;6}>>>>>>>@>%>n>???>>>>>>>\>&D>Z>\DB``m>mD>DY.>fr:>>D>>>>>>>>0x>>>>11<F>>>>>>c>>?\;>>D,D, D Ldc,!yS |Y$ _:z!"#&\&'4',-].02z5!568h9)9>AC;DEGXYZ^CabdfbgigikllGmpswx.WYA,BC۾ݎ(:D5cFic0W$BZ[bde efKghVikkmmn6oruNvwx8y{)|N{YwLz(D;Wk'yvUȐ]Z8c9(~W<^yg     qC+,,,-. /D122DDEGGGHIhJvxyqz|6}j^ZI7'2m=|K٤۩-!.+,m,5#8k:;=-@tDFcGGHHJ^LMPRVaWW[a\^_b*e(fx z}%WJ\ma^Nʴˏ̄8{ӞY#e#°/yŴzʦcVЌؗgٟ;!|nQ;P!K%9_D#yk!kHep% "x-  CoWLmCT!#o&5)+.1}4[7!9<?BiEGH JLORvU<XZ]`cbf3gUh4ijj=kalrIuw9xy{,|}~,y&f!E| ?c']H~&f:zu76* irmoƺǣ{ͨ$Y/g01I1|37 :<LSY\]wxz}[]O REYynޮ߾qN tHpK-ND , } tH6 !"Y#<%;':)9)*6*+x,,/023$569z:< >FHMIJ0KOUV VWZb[t\ \bdfegIhillwlm6mnyoqrKخhWu( ' $      !  t "P &} ) ) , . / 5 6 6 9 ; ;J ; > @! C E F F GS G H8 L \x ^ ` b e f& g h i j km nA p r tv u w y zg { | }\ + g  Q e K 5  1 { Q | M n # (  f Ɓ !*./<2<2/<2<23!'3#! f!Xt 3!!7!#5#!XBqBvwvtp }AA}pt 3!!75#[BmB"#tp AAvL !!#5!~P?2bl3Ot 3!!7!#5#!#33#XBqBvwvV3Ԩtp }AA}poOt 3!!75##33#[BmB"#2Ԩtp AA^o>1AV`q3632#"'#.'>7327>7.'&#""'327676&7%673&&'667"#%#"'327676&7#"'3272>76&7;22a\ +\`31>B88C<13]X RT424f223NK)9D<  4' *A  !!442332NL)8D43324486   8EW* l    u|  f ' 8.#$+.= e/8 f 'g   g ' >v &0;EO`hp%!#"&5!#"&5!#"&5446  72##2#32##32###!2#!"&5#463733'#7673 Z[6Z|DZ[3ϦuZ4Z4YD0>+'=+0=+V>>V=C D mm~i;;Y Fv,/[!#535#5!!!2##"''&##"##"5757433'7#&#"#"'&1354326654.54>32237"##52>54&54677&54654&#'*3#"&54654&8654&54>rnTo-$ % "&   ;   ) -  b     .  L(N.''lF#!  6 %   4 0 = ' / /3 ..  3 / / ' = 0 4 >w*2<f2654&'4633#"33#"567654##3#''5#7>726654&'&5463632&#""1"#"#"'%"##5325&'&5475&547674&'#5323>) B6#<BxB'3~9~0' l$ (/$ %cC5'# "$#"!*!)C>0*BxB<<#6B B"#E"66-"# ') KH-r"# !<)usu$k #" "F,:'! ).><" #"r-H JJ ##*66"E"  svu$;V9Se %&'&'&'&'45466303232&#"#0'66&'&'4>26&'07&&66766546646323276676".'&&#"'&&0#"'6767676676#"'&'&&76632767232%&465&'&'&'&74>32327667&.'&2332232767>6.'&'&7677&'632367676767>&66&&'&'&"&&&'&&'.'&5456""6&'&767>76326673272663'&'&'&"'466767>16%676322#0#&&#&#"%"#"'&&76&&'67>''&62'&1)("*)  1*  G   6 /O 9_+     G'<7K_(  4+'. D#"#31E3) ;D/&  i D 0y "FA/G  *<8?0d5A47. !,/52  "?9!$,&**#!" $!<"  3  19#      ! 8<     B    2 !v  #' (1 0B  '-5#)O#'!E '    #  V1$0&MT 4? #  s<:"|  D=@ 7DI014  /       6,+!   ?7     ->Q'+,(G@,    7 *  53  ?v2O632&&'&54>&''54.'&''5&5476'&'&&'&762#(YvE)&',7_Pc  W)FO0.  pt   FvZbYPq64pO[gOqNt)$ & ;.-;9 >W2()/-  6  b40!'!!'z0Hb00RSS > #',08<!333'77#7'7!!!'!7!!#'!'!!'7"&5477#' &A=XS"A9ANd cOۨ7zp)N}N$ *=DGNg0d/p<"""33\}F IjI{> #'*.148<O!333'77#7'7!!!'!7!!#'!#7!!'#77#3#"''&4776332 &A=XS"A9ANd cOۨ70J)~]quvswQQI,  , Ng0d/p<"""33\7~HŜo $ % % $ > #'+/26:!333'77#7'7!!!'!7!!#'!7#'7#!77## &A=XS"A9ANd cOۨ7)R)Qp(QHRNg0d/p<"""33\GwGFG9Iv !'AMehqv!!''!!'3'7#5315!'!5!#67##"7676554332##"''#74332##"54546372#"&545#73#'##353#v$]sL#S+4%<P1b,,     ,!*!$#{""ߵOK~ f@Wt >X.    ;A=$$> +3K!!!!4332##"5'462#"#"#"&54.'###"765054332##"''c""l98"6gLt=  V%S KA.}E#'##66;2'3#&&'3kw wnD)o&XXh(]v1%%153733'#7>>>_43_JPŀγ323332667454'.'&'&32663>767232667454'&&'&'&3212767667&&'&&'&767&&'&&'"'&76XuEEuXusuȻ$ u { < #  u %  |< T&G[NZEuuEvuvur  //JJ  30p   XJJ(-CcF:P4"E>?w<T6632#"'&'223&'&&'#"&'&&>7>76632#"'&&'&54766327>766767""#326545&&'&&'&'&#" a 5  Y " K"#$O=mtH\ -.uS#  Y 5$t$  .b  = ;'& /,|!#/4    I3F\69pVcT4Ze\_M.!2   5.#"{,/ b;  >H*-W#"'&'&##33032765'32765054&#72037276573327766505&'&##"7676321+"'&55'&5474776327327654�"0#"13212650454&#"1g+s6+s6?(%Dm+ h!*m4SR)l+k(R  PU  S    vHIMQUYm&>BFJLNPQRS6677'63#6#'67676766766767676177763767363363''5676766776173271327#"'&'677567'67676767677667677#0767"#"67&'323&'656777767670#36767272767567456767676767667676776767676776#63#63#673#3#3#11q 11       -    -)"8         ; + $ S + )<  `11 R  )( $  #/%S++  ))yA$ /" %- M @ " A ,+@ xb wY /%      *,     *?X^006326332#"'#"'#"&'#3"&&54667&546325#"#"'&'&5474'4'&'67656545476763235#"#"#012323'527676765454547676763233"#"323#"#"'&&'&545454'&'&23276767654545476767630375#0#"'&'&545454'&'&'&#"##3232#"##7"#"##523276765414767667&'&'&5454'&'&#"#5323233-B5( %'Oo3W4A&O02Q(C'!cG(Gn      s                         0$oO4W3 &+59.(D(2& #Gc#<<   <  :  ,  ,   ,    ;  ;  <  ,   ,   <%/7GRZ'7'7'77&&'6632&264&""&'462&264&""#"&4632232#&265&&'6264&"4 $ # # # @ZZ@:)Zo^CC^C@Z[[[o^CC^C=@[[@@[[@1^CB//B>,,\@@Z*9 @Z(C^CC^Z@@[[Z(C^CC^l[[[[)C//BB//E,, <%/7GRZ'7'7'77&&'6632&264&""&'462&264&""#"&4632232#&265&&'6462"4 $ # # # @ZZ@:)Zo^CC^C@Z[[[o^CC^C=@[[@@[[@1^CB//B>,,\@@Z*9 @Z(C^CC^Z@@[[Z(C^CC^l[[[[)C//BB//,,^vA#"''&547>54'&&#"#"'&#"#1"''&'677>32#"''&547>54'&&'"###"'&##"''&54776632667&55477&&54%6554'&'&#"3276'#7'7'7'37'#7'7'7'37'#3n  z  B',97, (B!   z  '16U{   :"1<;0#9  {\8:]O?%${ %|1,FF+4' ,2&K& '0550'0660(/55/(0550<-- D 7  "*( !("!(!!'"; 7 E  (>1l  E6  $""#"""2  6E  |3?B4 +/:lE7C',v(@66%0A)h;/ B%,0' 71221122M>v !#5!33r9T6666mmv}767#"#&542632321#"&#"#"&&54>323V.@$ i)2V,3A5$$$C2)\c1mH)6<%fF F4! <)/(W4;aBD/#"$|U3W>,'=v/>032#".4>654&'&#"73277#"&547632YuFEuXYuFFu$68&)$;II;#8HA)E9P ,E/&3FuuEFuuF7B1V,,62 !=3-|7P92&5)A30q&8DPx|,Nk"&&46632032>7454.'"#"&&46630'77'''7'7''7"35"'&'676321#6674&'&''7''"35"&&'&6763267&&'&'27&'.#"1235"#&&5467207%67776'&'7665&&'&&766772#"&'6626654&#"%46632#"&'3267.#"%2#"&''#"&546327>26732654&#".#"0'67>4..466.%>&&%35#3535#5##pnnpTnBoGx[[xGGx[[x'ttussuus$!!$"&" $$91!& (& %!!/%& ieei2,$ 6 & #+ = 96$> !34!KW$O8QVTHi ~)A%7-<=F()F>=/8&D)=*)n+=&'IH(3B.)L !%(Q.'K11+9?-*P24.@ <,_C.Y"*,.CabD0Z!*,,1]";<FddF/Z#:= .N1dc1OZ\!KJH7""6IIL57:6/#<$:BC-.0*!<@B;$%;B@eeo$!!$!kn߿nBoTonxGGvZ[yGGxtrsssu.$$ "%#!&$ (|"*-!!,#! %-  . 5 T$$((Q>" !;: 4$"; <8`@)F0[?>_X @&3V@?X6&@ /.9-%22401 />96) j/23".7,0:66$2v"9!!654'&##"#"3264&##732%#"3332?3264&l   ot  :X  i5  :E l6      %     >: l%#'##33'"#"'&''7303232765414'&'&'&'&5041054767632232321'4'&#"#"#"300#""7#32321#"'#7323276765045454'&'&#"##\iZP@0&6#'R  &+  5!#S %   #Um"   Bbb_*7C    "+5%"(.:  #&)%#"*.$"l  W !%)7FT^lv!!!3&&4662"265&&!5!5!%277'#"''7"#"'7327#"27'&#"76'632&#"'477'7'&&5474''76'654'73#3'n~]]]]wApqʈzx}~xzzw~}yy}~~{+5.?5;?.,=/5+;5@N=,.;;olf]]]ww;,#77## " 7##7 "" y |,y { t}>v9EI]a&'.'535#5#5335#5##33#5#37&'6#35335#53#735335335###5#57&."6776677&'66167667676676767) &02F,9@RE.Doi_/44444ST9D=3_5555m::      #B>19#78 D*8D=3     aU1b_Q!,=T5(7)4554JJ-+7=NN5O@   "   V  -*7 >vK"#&"#&"#&'&'&'&'4'564'564'56766767667332332253'&'&7667654'&'&#"'&'&767632323272#"##"#"'&'&54%#"#"#"&54546623:32654&#"#"&56'43232#""#&323225454#'"3:376#'##"3625&e49%%kRP" tQ7= t(0  y !  -=2!*3%%@&!!` Wt34  4<,S$$h% NLh,/   *)W) m36V.*K   1-   18@ 00#'4&&ymmmm* )7  >!+4321''&#"##"&=4633232776'654''654'  <]]8HHP<<7&&2   H8qqOIJ72@>2> !'-159=AEIMQ'7''7'77%7'7'7%'777''7''7'7%'7%'77'7'7/7'7#'|RSQ~7b-$6Q--.S};bb.:QTb.b.f/z. ܐ86~6 n$n.TRAR7.%$}TXAA%%n%n&X6~%A$$$&B&&%n$n$%n%>/.// >/?P`ho{463!2#!"&5!"&'663!2"3!2654&#463!2#!"&5!&&54663!2"3!2654&#73#'#73'&'53353#5#35373#'>HHHVH Z)99))991'77''77'@<3==<"0&3"11 .. 3!..!IOPA dd_"PT"DgIIIIa:(t(::((:37&&77&t&7<<==S0" &0""0. !..! .::N5NNZZ__MraB>v *?64662"&&4>2"&&"32>54&!732&&''#67632liִiiִFuuFFuuϾ]]_G^7] ;$>HRfv$,6BLTam36>767633273#"#!667"#"'&'&'&&'.'5%.''>75&'&'6763#67&%.#&'67&#&'"67%2#"%67#"'6327&'#"'327#"'%&%67%327'&#&'&#"#32767#"'3221.#".'02327%232761"#"%32327"#"'"3#"7327"%327&&5632327#"67 !67%745>77003276'&'3&5467&&'27&'6'&&7667%7&&'03.'3&5'6&4>&5%#,(! $)$@/"       `  "\ZZ3v+C*/)'&& + 1295##%!;'." .' @$- $!-$!$!!      %^!%    Y   %   3 # (%N  %i 8 )$7  #  l*)  \5?A'   % ,&   @  '"PY.                     >ZYYX4  "   7< $  >Z  H 2>;  + }x\AT&&'.667&'&'&7367.676&&5>766676#"'&'&547&'&''4'&'&'#&&&4>577774547765532744576633&&56'''4'&32367335&&'&&'&'###"'&767676322276307327631#"'&I /)6 3;=X(!Rl/ JX,C( DmB2 #?61      !- % OH  *+ C >    Lhx8-*3 &$<(iimbR:(2;+: S&@/" )   $ #(, 1! 8kV     >L 2:=M%#!"&&55212633032#!"&&5566754146323733'7#'##'#37337#'&545462354#&#"0#"'"55#327254&&jG0R/Q-P=#3)E+jH0R/>0\v:# < #7/#!(&')" :!  " !D (8[+C%(!<'2,F*9Z+C%4S]22̀ee ͚]   5!  %!=  -e.26:BJkq733#463!##'7''!'&54763!2#"'''7'76"&462"&462'54>3!763!2##'7''7'3#5-E/UЅ99^ 6(S"/  "88"8/88^BB^CC^BB^Ci /(S"Ѕ.9/8/9nIE9<3 _  "( Q"88"8/88ZB^CC^BB^CC^ ")-:/8/8xEB637'&5070!!'0?67#ijmdOondd )ZǞV||v\_ 6723237#5676755#5#&'6767&'275#4''67''673'5253'675#4#562326767676773367&'##&''3677#63#3#36##0#37#3736767767'&#"'706"1"56757&#"367&767655'025054#0#363675ow Dv9w&#"7654'650414''632277277'&#"10"1"'&##"'&#"7654''765414''7632312773277'2654'"'&477&&'#1"'&475&#"1763263EW|<79X1:$5 : >>~7>.>(8=|X:J+W97<|W9LT7>>: 5$:>>C7?XK9AX>8(T.$>w )1BQ_p!/>7'3#''7'57'7#73'75'"&462&"264'02320"&54666545&&#"32'332"&546265454&#"30'20320"&54663265414.#"32%'''7''7'77'777'7'7'77'77#"&462'4&#"327650'0212"&54666545&&#"3222121#"&54666541&&#"32'212"&546265054&#"32K&&j5sLL%"PK&&j5sKK%!OE\\\\1##1#>;UUyUT@2GH33HH3.BC]CA2%&&&5&   G.*3*@%At'1F*Q1}G.+3*@&At(1F*Q0a‰AD 4$$44$&[V{|{y\IfhIJhhJC_,K,C_]G$34$$44$(98Q98+!/0""00"6KK%!OK&&j5sLL%"PK&&j5A\\\\A;#1##1yT;32&'&'#"#"'&5455#"'&6776#"&5475##"'&775757757756"&'&#"#"#"#"'&5455#"'&6776#"&'&'323354547676&'&&7632327&'"33&'&67662#"&%654"36"&&'&'&676626767&&%67&673V[B8>H ]U<\Z% O   )"%2 l'!"$21 jG%! !&C)!%3 k'!"! R" # %% .C  (;zVTv ]9 0C nn$ZM;T!Sz  ;DC%;O U?? OM  X..  8DD %:P U/   04# ..Z[N 2%?K OOMy U?@_oUO PMK}=S0`G>Jhm ?F nMG/>v '1''!5'''57>v18Djnrv}!"&5463!2!&&'667!"3!2654&##'37##5#53533721#<>7>54#'6323#77#557'53#3#73#73#'##5&'7254.'&&7>357&#&'3232#"##732327650'4##325454'&#"##3#3#e(((( C!B2!!DCCDH#* + +$--"ǀd}\\----`c&"" (:      #""U"  #_B  AOIIz((((K YYFCCCC    %. ;A  L87$.0/G55     ## f9 q;4; 7 %*4PUdu232654/!!1#!"'&'&'&'&50576&'77654&#"# %54767676763!2066''1''&&'!"#"!!"#"&547737!'&#"#!13!232773) -b  a%-- )  -%a- ?()@- ?(( 2a4J%aa%I4a) /    / )    7/&3) /&3( /44 vf !'3?JSY!3'!"&&54632'17&#"255535'7573#535'7573"&54632'7&#"255(]",%"+LO )))O ^:45%3:45%3J&+%%&wO **)Ool>0"4>0"3?r).M )-  %934>8344).M )->*!23#!".'&50566!#"1!"'73[2 w  w,2Q  ׶, %     gui'&547655454&&'&"'&'&'&547454'&'&'&'&'45476632336767627667654'4.66%"'&'476763676767>2&&667&&#"'&'&'&'&a *J-/U !  ' C>_L5O q1    -%   P    %& /Q46+3=/* 4;-- -9&) !M. GG373  j1    -,;33#6   >w^ch"0#""#"1"'%&'656061434707063762301275477047074725277623211555''5"&0&4&14#41&1505'77770.'''77';RQ6R`DR;RR;R;hZ 3__O^^!.̝-쭝Yq-OOOc-X!-OOO .!-"s9OZS}pk/37!33#3##!535#335357!33#3##!535#33535}9???}8  ??? }} ^???? }} ^????v !34"#WXXWW+Ⱞ>K> 2S5463!##!!3!55414766321!!"#"1!0#"&'&=!"#0#!#"&&545>303323212#"'&''"4#"#050#6767654'.'&'&+"50410543232' y}o GB H$\    |} '&&% +*  9(%KnC  8n    {,,PTS ^] k>M'&'&'#"'#"'&'&'".545466732660'&'127&'#"''32>'&'&'5327#"'566'76'66'327&'''676545>'66'&''67&5&&'63&'747&'0327&'766767264&"462"&264&" !F'&5J   +F?+[]Bg71 '%7 '/6&$Y.#\$</!,  d* xL!%  A $!=V~ &!1P 9,z-?--?,&&5 $'$ -2!  ?  <_D ]^Q?=E,B   !;  1# Rf!-+           w  <#   7}8,;*C--?,, &&I  >M[k '&'&'#"'#"'&'&'".545466732660'&''67&'667&>7#0#&'&'6'66'66'#"'''&727&&'#.'67326767#&'''&547&'"'>7'&5414630322327#"462"&462"&264&" !F'&5J   +F?+[]Bg71 '%,9 P14!& { *!*d ( !*%._7W!* <$.WH8/X$&8a 7z*P V=!$ A %X/} <* $9b K=a8& -?--?&&5 $'$ -2!  ?  <_D ]^Q?=E,B   !7+< ,7}(         O ! *-"1D( H=   #<  :?/&5(!R,=H?,,?-9&&I  >v,?Q^p)Cp64>2"&&6765'"3276767&'&5477#"'&66732654'232"&5466.'4626&'&'&&66'66&&'&'&66'66767>&'>767>&&'2>76766.5667>36.73.7.667.>7'.>.'&'&&>27676'&'&'&&'&55765&''3#>FuuFFuu~ -/98A: E36N_ &,&q51 =k/)     . I73F 0G  ,_G.0>K[0  Qj72=HIKj .(IOL 6Ma_]<"56##VN8(FW_WK!5=*+&WCgupQ>.<+- (L/$<;<40!qHN$7$0$D60! 22  )4N) 8  *B-2= .7$3   ӲuFFuuFFu =5 !10H)9!80$ > &x   G l@TH "VM01HX\SD4>*- 6m1ENSJ=%P,SU 6V 6@:90(} /$' "0 +31R:)! D74J" '@   .O6C4Iu+AW*-8EGJw95#DMP*=EMJGEP;4 "%%" BUa[Q!6<() E?1 ;KXVJ8.2-=8Z* -LLM@0  A)  "!   " >(,##'5#3#37'!3775#'75#'5##'5#76?M]eDCWT\nFOAA9>XPV)-MeCSrpFO?<--@Mv 3AVhy1;BOU^jv.D]kFPny-5Ne}%/7?EPYbjox'0:EOUZdk "(.4;CKQ[dmu}%+39>KV[agms     % - 3 9 D J P V _ e n v ~    " ( . 7 > O U ^ e n     ! ' 0 6 @ I U [ m t z  > R a l "4E]AQ[dv,8Uc4&'&'&572#"&'67676767>76&&3327>&&'676"'&#"676"&'476765620&'&766#"'&'&676&7676767667676&'&'&562'&"&""'&#&'&#'&#"&"&#"'&#"77&6&5&''3#"''7&'&'&'&6672232'&&"5674'&54#"''47654'6#6#"76'&6#&''676&'#32766&'&'&>65654&#"3445&76545&7'&6"056766'&7676567677&%##"47676'&46676654'&767654'&#"3'&'&'&#"67764562#"'&'&'&'76&'&'&&'&'&6767276#766'&&'&'32232321233212327&'&76766&'&&'&'&.'&&>223'"#"#"'&32766&'&&"7676767632322&67603650567654''#"'&"'6#"76&'&'"323>54'.".54676'&7232334''&&''767'&'&'&'&547707454&7254676&#"'&6676&76654"454'&#""&763276'&#"5463&''#"&'&'&'&#"#&'&326676&'&'&&7'&'&'&'&6767#"'&55&542&>246'&''"66767654'''#"'75414'"60346320.'&5425'&#"6747'&43&#6775&'&4'&6747'&34'&'"'&4'&72'&'4'&74'&677766''32367#77#&&"&'&'674767775&'0>"76767327'#'#&6476724767'&'"'&'&3#&&'&767677'747670&&'67&'"7''5&%754767454544&776%76545'745455&76%745461&!32'73&76760'&300'&3&'&54%766545'&%672"7054%76323'&#"&''30#'''&7>#%032'&&'&'&34'&654'37&%632'6%4'&7'&''67776'34'&74'&67%705476743767474'&74'&&31&076743076747'&&'&'74'&32'&/3674'4'&676''&''674367475&'&321&'&3676#4'&54545476#33&'674'&74'&4'&!7&62'&#%654'727'76!&'%67443&'&7&3#&2&'"?&'%676#4'&%674421"7#74&'327054'"%4'&%&54'&6!6744'&73#76'&'&%6740'&%674674&31&'4'&7'&'674#56&'&7'&67472'&6767676743237&""4'&67'3266#%67433&'34'&676#"33&'67%674674327&#"'&##6744'&674!'&'#6742567676776767466747674674674676#"223&#"76"#'476#7674632337&332367217#"1&761"'76'054726&'&&12"%'&543&32#"#&76743676'&'&6747564'4'6747'&7'6747&7&&&'75'&3&5&'&'&'"&'36&'&''556'1&'67654'&'>77'"76#61&67'30'&541'&676"376'4#676567632330"##"'&'&''&5654''.'&&'&56545#"'&'&'&''&'&'&'&'&'&5476#'&54&'&'"'&#"&"�#"'&"54#"&"�#"&'&'&'&547676767676724236545476&'&5476546324676724767636767676256767676767676767672767676767432767476676#'766&'.4'&'&7676766&#">76&632'&&66&766#"'&3266&'&1665&&#"#"&5476676''7476#&#"66'&'&%#&54676!32&726#&'"'&766766#&'&'&57>32'&'&6767'.''767#'&'4676&766'&'&#&'&'""'&'"&#"&#"&"&#"54#"'&32766&'&&7632771'&'&3676766766"7636.&'5&'3276654%#"'445676767677>64&&'76#'.#&'&'&#&#"1"'&'"'"&#"767632"'&5477%7654&'&6632654454&#"#&25&0#'&7632&'&'&'42226'&'&7633'&'"'&'&'&&767232#"&'&'&67672#&'&'"0"&67663543254&"&#"&32'&13454"&&'3276'676'254&662"75'67s%.&,!    s  /*C# " I@2U!!B'  "   #<-n>9&   %  4<<3  $B>@ :5 2 e% #  $#  h ' %$ 1      +)  "  $  2  ))i \ i$2%O*35  '- $A   , {   $'(*    [0     v $  ; %6 + %    W    &&   &|  $  -.F+   &1$    $     <W 1 5 c  ^         # "!   HR' & &4$ "      # )   < [  7-6 ^ p/#0#,    (  ,j4   `m{   E2 g*I8       F    }q ,VI\V ]  t9      \ Q>h  f :    a<  5- 1  P@  hH5 X "    ' 3V  $    x  sACMFl $        &  0          "      #      -'    ` "*I/  !  p6p #>' 689-?e '7i3 1F" /($(P#! .) 7 U D#3 %  /)% *O   5 )*$ ' ' % ,     [ '&!   &3' +:-'.  &     %$d 5t +(!'=     8 &4 )  7              "  "$  ."    &5:#' (#&     +8      "  > 1=4+<3IMH#D)Jh48G! 152&    =%$ 0.-  !" $#$ 7    / A*?B(;EG*>$9[)7'AKBD;OH%(2J QI  $7A&             # "     C/5  & Z1 +b   :8  !  "-1  %-@#        !        )       *Dt%2H4" 3"   [w%#".4>32&&#"326Ͼ:xYvEEvYw:d;ZZMh454'&'&'&#"327676765045%%1#"'&$'&'%"&'&54632&&#"3267   x   pq"xx""kGw#q<#5LL5$< YZ  #  ZY hl YZ  ?Ax9^64>2"&&21'54.#11"54>31'54'&'##"544>76312%"7547>122544.'&#54>7%53!"&55463!2!"&55463!2"3!26554&##56673!!>FuuFFuup1R:. {)'./&){ ,4+Z !F QQ ̕J d"L)  "d  J $:% 4"OHffHHff7-EccEFbc%-7"&&55466321%655%35".65%5!3"&&5wvvwYuF`V`vwYuF ``Jvwwvp$?%%?$%2/&UU%0//%?%%2%00%U%?%$?%(rvv+AEIMQ[ 0Ux%7632211%763221%536765455'&'&'"#"#%%%737#"#&'&'&''&'&'&''577''4547>377''763#0761"'&717'7675&&''&'&&''5"#"''&'&''53#172675'7''53'7'#'#'53''&''7'72#"'&&1&"#"''&'&''37'7232767654''&54767'&''&##.375&'67'7'"'7654'&77654'&77654'&'7'7&&'7%&''%&5%'77%7r@hahA @haiA !AiagMOJ|B<~>    ;/ 4  $~h&+ [&+,&&# pX9. rYr L[M <;334<<3;~x;&& 1M0y<'Oyn0+ %;b<  ;W <;f      S-1V/ 'B/[> |77{:$ N=T#"Y q#"Y g*p Y!#iiii6j8Dr% " &&?/ xR+h ."&+# )$P*$$) :"TB$; "U" ;Tx$x^#vy+vvv.y++u*> #xh (%"'(3> #yi (D.+ " >'!+( ix7%>?yt>> v**~&")X-=" 2 7|2Yr# DI >2V|3&45567676767667&%67&'&#"5672236322676670&'&#&'""0"#"67676766766756675676 GQZc),.(E66,6$J  %+?D;4/  JLomfa 1'' $,%'')+/ ϺYcVZEQGZVcY,)cCC#y"D9>.%+JUUZjp;l#(gz/5$[ o# [ 6 C$9D!{" 6 F.>r,V%'&'&'&76763676'&'''&'&6766764'&&'&&7676M ]^66 BC![Z <-s  om]]66 CB1$ ;: 9.s p,<  M ]\67 CC [Z <, s o`\\66 CC1#9; :-t p *= ge #'-15DHJMPRVX[^`dhotwz|  !%)+-0469<>BEHJMY[acegsvyz}  "$0368;=?FHLMPR^adgkmovw{|%1?Fnt|u#0=Dkow{'%'%''%5'75'%''75'5%''75''02"6677%%'77''7'5177'7''7'5'5'7'4553#'5574553#555777'75'7'77''7'5577'7''7'77''7'54753#'7574553#'557''77''7'5577'7''7'75'77777''7777#777'7'%'''777''577'.'''7'5''71'3'7777''77'.'''7'1''71'3'777''57'.''''77'51'3'777'''''7'''177''1#''''''''''''''777'''''4#''7''''''%757#'3167'777'775'''7'7'774577#'1'77&'''757777777777#'3167'777'775'''7'7'774577#'1'77&'''757777777702#00#0###'0##'0"#'"1"1'#"5'#"5'##&1415'1"5057'0"&417'#0#414375#"154575'"54775'4154775'&1416?'4416?'4563775463377476121774720377632137632177623763272121172207303113201674&#5767'&&14#"72076032767746541&0'&176547577654745#"77&#"''76320765415457'36&3276#"01#'11##'0#'0"1"1'#"#'#&''#0#"5''"1"1&/0"#&5''"#&5''#"&55''"'457''"'0457''"5414175'&550775'&4375'"554335'454337'046217'54121217543233742743376122702273032010017414'&767'&'4&5&''01'32176'&5&63'&'"325'#54''34''&5417&'''&#"5543&67454'&%212#111###"1#'0"#'"1'#"5'#"5'##&1415'1"5"537'"0&417'"505057'#"54775'&54775'"54?'05054377'0456377'5056377'46377472377436277672177433037423743370327217232656&5767'&&1&#"#721#76032767766505&''45476751776547345#"77&#"''76323765055437&63676#%'5'5'7''5'5'7'7'5'5'7''5'5'7'5757757577575775757@k>@k>>>0>>n0\@k???o0?k>??n/B9PPkkk44Y3PpopOp ,¾OmpnRp88V2QpoqRp::S445X4QqpqPpTQ$qMq.Rq.P$qNr.Qq/JIDD2ww.I )* "  !-"  !-' !) !.  "! ".  ) +`.-!/- ., /, &  1 9   22*"  + 6?   6 3( & 2  9   2/$H  8  =  : z /N"-",,/"-"",- Y!-"",-; X","",,  H ƍ8:9O n-B,P-OMOJ ) %3 )5OZGNZ6 +8LOD8KNKm-B(M,PGK6@7Ym-B*O,NKMe8A8]B4 +7O^KN]8 .:MOD9MN7:8N n-B,P-NNP!2.- -NM3., -MMi&% ,%R IM K2 @   ! 4 T2 @  ! 4  3 @     3  2 ?  ! 3  2B    "6  < %    %3  3; %%&   /3  3@&    %3  7; &&%   /3  3@    ":      $'    G! -    U4v?   ":      () * %S ) & ) &R * % ) -( ) - ) -) ) - >dE%##"'&55654'&#"#533276504154763233#&#"32732327654'5&54767&'&5455454'&#"1#536323273#&#"#"##2#"&54617&#"532#535'7573#535'757372#"&&5467&#"532K 2+ +4 "f"!  2+ ,3 #$)##%)K'NK ' 712#1712#1#%)# )K'NK'#/IN/$NJ/Q! $QQ! #Q.IN /N%0 .611<611?.'&''&''711"'6?1&&'4''&&5'7&/7&/4&'7&&'&'&/"67616765>5674>?166?454'56746657&&'&'&/>7656747667657&&/36766577677'6676?#4&54767545477&'&"&'&'&5476467636#"'&'.'&'&5476467636#"&462"7".'&'.'&5476767>763232654&&#"&462"&462"&462"7#0#&'&'.'&547676767632264&"76#"'&>&#"676707632'&#"#76#'27654''&54"#"77677&'&''4'653066767'27#"'&7236767Ė44  4Z=-D`RR`r\?       #8;+@+    2+     ':    &%         n        , ' (       % ' \>X>>X           n-@2-@?:>X>        @.3@--@CV<iu%#67%0661#6&76�#"#&'&'#&547#'>76326327656.'&'&7670365450#"'&&''&'&'&'&1''&5454145676776767673326767654'&&#"32QU (  k J %g ) d 7 /S(% +OO     M     Y   q96G=+##-" >M#'  &&$ZbU0 EQ&4u"C3 *6  Fr   A   Iv !Jo!!''!!'3'7#5315!'!5!632#&#""3232761#"'&54637"#"33325572554##5463235&#%##"554332##321##'6322&#"#"323276#"'&&5454663v$]s*37$9LN? 0)) N933I    ߵOKՌ+ =5=4*PRG] $## --=# Iv !Jm!!''!!'3'7#5315!'!5!632#&#""3232761#"'&54637"#"33325572554##5463235&#1#"'"&5&5463632'&#""332766322&#"#"323276#"'.5&5467##"554332##321##7232&#"#"323272#"#"&&5&5467v$]s*27$ 8MN?&*0(( 03?- !      'IN933    'ߵOKՌ+ =5=4*PRG] 3-## -B3-@%(!$$&~ (-=#(> 3e!!!!1#"#"&5463632'&#"1"3232767"5543354547663232320'&#"32###0#"=1#"'"&5463232'&#"#"323276c"l%"+LefS6?E0" &  019E 5f!!!!0#"#"&5463232'&#"#"3232767"554335454766326320'&#"32####"1"557236321##"554&&#"1"+1"'&554&�"#"##"55#45436c"l%"I`aP2="% % ..635P2 2 39Z 11  caYl2 IA'9/-*0J q  /> 7i!!!!1#"#".5463632'&#"1"3232767"5543354547663232320'&#"32###0#"=c"l%",(  eT6@F0" '  009Z 11  ",<$]q 3 NDMF10+2'3663221232#654'&#"#"dZ393/d!$1-'&j,&+/8< u6*").2 v. $(,048<Hrz 2#!#35!"1321227!&545!5!5!5!5!%5!%5!2#!#3#"&&'5&54665&547663232""#"1"00'553373&7#&'463"01&673032673#"&4632#.#""'66301072673#7&&'7.5%&'77&'2673#0"#"'7"'6632&#""#'```````""5"/Y34 C/  ,  34*(@ . Z;D``D;Z .!02F 7 #@/ *mD=] . yP 8)5I 1'>#',S| - emM A[Ep+SYG:Hg}$"45 ,}  ' $111s"05>0C0  %6 0&9J``J9(F2 7 !1 >L~M;Nh+C)Z9!#?/ *;Q,!-&1:!;5.3lPcN AN?K^5$, >v$AJV_gmu%7303"#&'463"0&673332673#"&546632#&&#""'66312032673##.'7&&5%&'77&'266730"1"&&'7"'663212&#"p8 -N(; U$oN.@ H%T <(EBhLapI|JaLiCQtsQW D'pdMǃ 1G,XxbOd| )?2 'I#kZfK MrS"*?P>^zpJ|I{]?PtQQtcOe}a/R=E"^6X E(o6J?'Q_6aXLTQWPf; 9(428ցg|W7'&7676323&7676'03276677'&74763036'&'&67667.67>'>70#"'&n $#$87grE !*+KJ~N0A  ".8/0  NE3 >7.r >>C//:;Bux@Hi  5" ic 61"#&$ (&, ;yS )mpYYS7@  ,  ` '   00  *.?" '#     !78  )5/2M'4',*%-%'%$!./ &   a)-A\%"#"'&547&5476323232&'"32327#"7#53"#"'532553#"45476303232#""1"'&5#"#"#"'525054'&'&'&'&5441476323212&'"0'.466775766&''5>  ,  $$9 $ !    އgbbg C0:cM4` ! :          4oгo4(IVd4NkD4  4!>v%7Zp".4>31327&'&&''>7&'&'&'&'#"&54667&#"3267#"#"''66554&#""632103263213332767654&&#"1YuEFuuFFuY#%" h)0&4!.$"5;h&'E<jkc757J(-zV"     la71;4.05hKB4* jkEuuFFuuFB48 KMZ4"@41b1) e h%F;F4kjZNMX3Vz C:..) /YLhiI(%6kjw( %66.''&546;2>54&'#67672'&5&''4&1'&'&'&'&5416767'&''&''#"#&'&'&'&5'&'0"&767654''#"'032676323232637676767676767632767677&'&54547672654&'&767654&"+kLLl+3(B0kkeO\     / [[ .        v*=!;M;!<*OpP +4CLkkLC33I 2BQ+kkW )!55!(               5!8SJi  hKS8!50DD0Ay !$),AL ''%53)7%!!%37''7'!"'&547632#0%23!34'+S')V===oTF]FWXSSo[\6<UYVnZY?6767327>7&'&#"&#&'&#"&'65045&&'&'.'#"#&&'654'67#"'&&'&54727&547676767&&'>76777.*  AM        1  f 4    *8- +"%K'  X vS17$    .(  *  . **84    !# )1!+   I/9% *+)B"  _+ ?v%-6?'6677'633"7.301&7&73"5"'667'64'7jKB5J"79)y"T+r8Fԃ :q](BGRuqQd8";a |"T,r` kJB#9()&"7B#])nr)B1Q:~JX"'Q'"7::>v !77%'?7'>lާcDcb'd&b]c]ldDd]c'c(~b]c]evv&&+76326767&'&#"6632&##"""###7'.5466736325054&'7Plk56A<, 8G40{z05 ( ;,4=(8]64Z6B74=7m7>kmC$% */ wv $ *,6Z"A9^86^9&$DP7>v&=64>2"&&&"27564''&47764727764''&">FuuFFuuF((B((ӲuFFuuFFu(( )( ()?4Vx&#"7>76&'&67766'&&&'&>76232765&'#"'#"'&'4667367632&#"3#"'&'454%67##"##"#"'&'&'.'&'323232 %3% 4".F$*1+7+$ 9$3+A8t {n 2=; xl{_%<+" (25!.&0 ,"0]=A(9= :72  # >a m"    $$(  "2  *& 8 $;:#~qZ $& *&$0+q~?4Vx&#"7>76&'&67766'&&&'&>7667632&#"3#"'&'454$232765&'#"'#"'&'4667367##"##"#"'&'&'&&'&'323232 %3% 4".F$*1+7+$ 9$3+A8%<+" (25!.&0 ,"0 {n 2=; xl{~0]=A09= :72 A>a m"    2  *& 8 $;:$$(  "D#~qZ $&( *&$ Zq~>2& EO!!#'#7%###"'&5454547676763221212&'"132332677#533#53V0??0U!-!  EnEM**~RR~5!. '  !""4v*Dh'6767&'7&'7'%'.'&676$767676767676767676&'&'&'&5532>7#"x?s5\S%r6o4TP$:+4چLYamC& JON._Izarr Q{J~8IA :0 &B:y=@>2 4(PIc+-7]>13Om 4tZ2BMS^j3HYk$9I[q%8HZk{-GUn,AOdt /Tdq{%4AX} !7CO\jy   # 1 E T \ j ~  + 8 L _ m {  4 F R c q  @ T f  % ? U ` o -<HVfw (4GXbq}.MRg3<XjrRT\^di jOe*B6[s !!&!G!z!!!">"~"#F#'?'''7'7'7'7'7/5'"'0"#23462#&'&47"254'3254'0'""#&662#0#"'&550&5676230117#4145151441<50412703#&'041445330215&7670'#4'4550547632017210"0#"'&5456%676&467"#"1&#0"##""#"'&54762'01"13276&543&1"0#"#"!&21251263225405454&670"""#4416730103&&"#&#&&7632&"#22665<%#&'64767"'&50547257647632233#"'1&5&54466%"27654&5&#%2&"#&'5&76%111'"'&5473"#303"1"##"1&'45471360'1"#"#332366&70'%1"'&7470321"'&743632%&&1'654'3#47632"#""32121266164%61414'67672'"6##"1&#""&'5Ȭ&'54763%566#&#"&''45467630&#"#"73&#&5476320235#"#"'&5&4767;12"#"#7654541&'&54763272&&473274&673203272&6#&'�'6&63#"'445232'2#"'6&&4&503&5#3072'02""#&5475232654#"#"'&'&'13272&454736%632&5&%""101210#"5&14330'1367&327245&67"165&55677673#""1"745630653125313223&'0321###"'4'45&63212.#3276&7326751""#670"3654414'&765&&5#"2#"#"'&'3037#6276'454##67454472#"'&705056&%06'"#5'6#"'&67"#4#"#"54762254131'75630726&'##""#1#12#657272307"1"#5654'632503227&&'&3632223&'#0#"57227474"#54#276%25&""&5&6654'##2"'&5544521216'232727&'"'0'124454762&547232123#"#"0#"2234'&'"5&4747072330"&5445&474363230454151047&5"21465414'&'56741&#"03#6%01;&46#"5674646521"&'&51"1"&245054767010"'&5414'31&5054%"514645&##"'456'#67&50&50&30076743&%3455472&&5&'&5054523"'363233'2323'#3&5'"330#"'"121"65547541516545"5"1"#"'637273254%7276332327"#&'"&&'21'2"01"'&'&1232"#"'632#""255#65#&'4447"4571670#"#"2&'3263%2&'"541454&'&'5#0#"#"'&50554622"&554634632""#&'2##&54662#"#5<7254632#""&&&&5045462##02"1"=0547632'32654&"##"54626"&54632"&54633222#""&5546##"&5463232##"&5462'2#"&546#"32764414&'&&546632#6#"&55467#"&5&663212'4236'&54&563'#"3276545&#23231""#&546#5&"326654&"&5432"13236654&'"&54763032132#0"72#&&54721#"#""&54456326132'2"&746""&5463274#*101326550&44'"&4632#&"03325':1343367454'&76505054'#0#&2'&54742654&#"3'620##"&'454#"&5632322554&"0'2#"'0650&54'2745454#""#"2"#"#50041<72""#032676'33266454'&0#"#1"'445454562&&7632"'3654'&#"#4#"27650=4''"&45462#2#"54507460#"5&32'&54763212#"7&"32323656'#0#0##&'4322#"7463'2'4&#"'&4'0'6322"#"2"&5054#"'&76#3#"#654'47674"3654'&541231"3"23276&7572365"'&547523342103>7"%202367445&#"#&2'"&6'454723322120"#"#&'&&76322323074763030:'0'63632"##"'"545"&554632'132556#&7632#"'&54765723#6322001#232#5454761#0#""1##"'45476303272554&"00"#"#1322112714546765450654'&#"#7"326&&7#"5546323270127454'&#"1"1"#72#"&&7432330632#"&&&"27654144&"32674'"#"&54322#"&547"#"2654&##7"#"#"'476""322365654'2##""&5463236505"4754&#"1'&7623617#"'&76766#"&&54742"67654'4#7"#"&&6&451&72&476#"7230"'45453722327"#"&&54547212232703545&3##"'&5476322420&011"1"'"54734&5763033&"&46322##&'45476"&54632'##"&66"#"#"1""057654'#"#32374&72"&55670547""1""36545&5"#"#"&546332#1#"&4632327&#"'4#'&547662'"5&7620#"#""&5543#7654747232%1'&5547216323'232#"'#&#"''3422333272%5545472761676?#%'&#"##"##"'&'&'0'&547'&5476767676767673676576767676767676327232&'##76761676?676176767677632#67232'5'515&4755414763&5055454'454763221'&'&'&'31"#"/#011#'&'#''&'&'&'&'&&''4''3231"'&'&'0'#"0367654'&547654#"1"'"'521276753057#&'&#&'&'&''00013674617676767677'J'&##1#+0"+1333272506567465#0763672"546327676'&''"#"13'632232"'&54%6"54232"'&54763%367620#0#67276766763"#""27672337476727667&'&0."5&'&'&&#&&"#*#"'&54363623232354'&'&'&'0#"#'&'&'&'&56322323121654'&'&5476776'&'&'&541414'&'&&'&54654&'4454'47654'&'&545>56#&&76746767654'&'&1"'&5476'&'&'&'4763232767>767276766076762233211'""&#"6567676726767232332363265&'&47676'4&''&67676'&'&'0&1&'&'"&#&'&'&'&'&&767672'&'&'&'&'45476322334'&'&5456725546323223&'&'&54763217>72"'7676'&'&5476545445&'&'&'&&'&'&"'&#"#&47676'&'&'&'&'&#"0'&''&10'&&'&'&'"#"'"'&'&'&32763133211#"#&#0332276323232323&'&'&'&''&#"#12327"654'#041&''&''#133&&'41"'05#231070'&&'&'&'&'&#&#"#"##""110'1""1'02'#'510"'501327630212&1&'&'&'&'.'&'41"6767656754#&#"#""101&475674615&'&'�"1&'#&'"&#&'&&'&'&'0'&#&#&'0&1'&'#0#>7677615#""1"0"##"#""1010113303221231327263727672723672253767672767676767676167#0##"'&77337676767675267430216"5007656#1"'"54321;27212'367414'#4&'&%&#"'0&12332327232#23#"$676773676770767>76321256505&'&'&'0''&''&'&'#0#""#0#0"1367676767676762676767667676767654'&'#&'&#"#"#""676767674&'&6176'376767654#407677&'#"'&7667623327630541&'&'&'&'&#"'&'&'&132233025576765465476767676767454'&#"010"32776#&&''"#"65'6541&5&'4545676565036'5'&/&'&'&##174656545140564'414114'&#&'0&1&&'0&5"5&'10"1"2&1&'1.'&'415.545&&'041414710=61504511#4675045476767670616767061567>767675&&5405&&'4410&14'4#"1#"'"'#&#"1"'"#&#&1"&#"'&321231476714376632111501212203205100032320323233322324250506715672521506166767&#"#"#"'"543367676303254##&'&'&''0312741&1.'&03767&'&1"#"#&#"32767654'&&'4'1123#0#&'"#&327654'&54#&'0&#&#&0####0#100011#10101033010301300320'&'00#&'&#"011210111#"'05"41&4'4'0&141&'4'&'&'10#5&'&'0"1014'0&5&'0'05"5&5&&'5&545#011100&15#&'&'&'&'4'54'&474##01011110767633667767767767067673277677254414744554054174417676766565670506165415166567616767510616741>5655745614'&'"'41632302032121466&'0'&5475117546765474646510"136&#"*1670323276763276767632#"'&'&#"#6765454'&'&#"&432#"#"'"'&##&'&505&&'&'&5&547212327032&'"#0#327276&#"1'4#&"#"#&#"232763232767272502120#0'&#"#""'&567676'"#"'&#"0#"'&7673272322"'&'&547672'"536541#""#"###0#3202327276611"#"#""#""#"'"1#&'".1&'&'&'&'&'&&5&'4#&'&'0'&''&'0'""&1&'�#"''&'&'&'441741676767676723632;276767676767767631#"03276767671223332311676707446&24323202'45654'&'&'&#7276323223232765676'&#"#"'&'&54765454'&7674'&#"#&'"1676367654'&#&#""'"'321632326'&#"#"3276767676303227650'&#"1''&#"#"#&#"327632327672767&#"###"'&'21233676676&#"'&'&'4'�#"132330326#"#&5'&766&'&7>76766761'&'&&5414706'&'445>766763221"0#"'&'&1&172747654'&'&76762367216767632"1"'&'0&#&760%'&&'&'&'&'&'&'&'&'&'&541476363232776'41&##0"1"#"'&'&'&5476265&'&'&'&'&547632076554''&'&'&'&'&54743322323276365&'&'.'&5476 9- mo!'Q n  >  /        0 y  % 5 "   / 7 a 7 :  Y c 0 K !q 9 5  ,   =  C Tr km   R F sS   2o u >V ^ v1v  If Y q / )B)F^ c >  hKFyD O      o   }7 AT#   ? ?  F+  h%  jw 4   U Rk X !      L.  0  k UV &   6     )1     3YD 7  '  C?#         .'   R  # / 7        D,           "$  %.    +    z              9tB: '&L85    1: +  N/4/  ;>  2  **   @     ,(  DD $       $   $      "        3 $"    v          ;%  f              /           $"    3  -$^! <Q   ;  ]8     #     33#: 3 L            2"M' T          CB   #!  %   E   '     4  l                   # 8  m       =        **    5 33   ?      "$5         056" *@' ?K2.,F G: @  Q` 7 ;g 3 _     1 u M  t[  X(   7 62  % u  O*>7  \8KA  ) NL F,   H K?   $   l QwX$ a w? O I ( A7q ' ? j/["%,  "q vbv  ,At%   J 8 r  /7O I q -  #6 (J.)  \ p 7R   C;   #E        K   f  LN(! \m9 * 9&^ ^Ko8 < =   " P   (=! L   $   . (  N                      &  ./           ?  l "1?  8                /1 * 4 ' )   ** #$ (         @     $   /:V +          8   }   G    ?S             U  x        7            r 4       " e $     $ $            ?  .         =                    1       H  V           $      \  A     6H"          $$+ :       *Uv N%%7%632654'&'&"30232327632#"'&'&5476\fe]  M] @2 +>)*5A@% Y$4 /     ) %   6 $Vwrq"'8@NRZeims{#'+/5IMY^bfjos{!(-59=BFJNT67637&'465'37&&4547&&67&#&'&#71&'7&'167&''&&'67267&&''&''2323&'632&'67&'6776&'67#76#&&''&''677&'6&'&54757&'&'7&'&54767&'&'&'767654''.'677#"'&'67&#04677&&6677"635"&'632065&2&&#"&'6324&17677&'&5'477&&54767&'736&'7&'60&'&''66"&#'&'677&'67&'&67'3"2&#6&'2320.'36732766%674'&'#0#".'&'47677>&''5"6'677&#"67676#06766&''67&  ,  "  "  "  !   P# u%%3%(.+(CJ&(1&2I$ 0 A/)(I*)%1N  #*.s" h ),  -( H^/<4 (-  )6& A P+0[  % 1 . 6((+$%& -'! 3     >75?-n 387$ 1c46BUXD  x4CUo1_G4 # z1R) 6 1r   H7=$    6 0 !  = "#! !$1%%("!b%&!%!=  #p) + ' ) "* )       [ L*+  RF:K -(   (-   9   ,  ,) $ BPB/ &H<0- n.-'2\*0.@]=)'3R:!.$#*)+^%1-+"k u(/)&] 3 0r  .   (! s   4; %OR=+& Pi8 Tbw/;H%;C!  ID'(+O   !  #EwWf%"&&'#/&547'&&''&6675&''77>7&4>7763>226'%3276654&"V '!(+)]>< #'.Q$ =>V),(!& ]15#&#Q$ =>qO1+6[[&?< "'$.')+*X*-(NW'2#'0#')+*X '!)NW,7 N0@[[@?'S7>##"''.#"''&547>&'&'&54775632>766747632^:_MhV+:=-.  .:`JkW-/$%   O^l".%1V )#& o _k#.#3U " ! p>"07%%7/3'3%73%?/ XE)vjR a*@ rXXSv8fR %TM.'   ^T5.> 433##5#53'3##5#535#"&&46632&&#"3266I011000m00100A3kihhi5eVGY5OqqO$A3q010010010010aYjhҳg3G+a+4pq,^w->Mht454'&'&'&#"327676765045%%1#"'&$'&'%"&'&54632&&#"32677#5##33537#5##3353   x   pq"xx""kGw#q<#5LL5$<m YZ  #  ZY hl YZ  ?AD2>J%#"##"'&54545476323232&#""1"#"32323277533##5#5!533##5#5?F fFDLLo<94=B,-*+?B8JIIIIIIIJJII1JNhrTQu 04DB2/#IIJIIJIIJIIJ >f#Act."677.7>""2677.7>7""2677.7>.'2367'&&'465>6&.'667'Ǒ>&&".5667&&'667>.SQpbhN:  1CK?EBXKH.Xl94A1$  ! .)3$A_gQVObJ7%02+ -,<34$Gm{^.*5(9#.-KaiN8RUDK-,=58*99+21( r Ts{\VQeL= } $h_sS:RT&3882' +K2fiRQqclRB,D*RpC$   -I|RGh6&#2<-+< @ #;$5L& .P66Q*3(', '2 * &E0 ,=  2F 6$  7}[ 5Q0!  d#$  -?X'   %>>x%#%"'0547632"3Pt=tUX7ss<tQVr^w->Mh454'&'&'&#"327676765045%%1#"'&$'&'%"&'&54632&&#"326775#5##5##3#33533535#5#53   x   pq"xx""kGw#q<#5LL5$< '/& &/' '// YZ  #  ZY hl YZ  ?ACR33533#3##5##5#535#5335##"#"#"'&54545476323232�"1&#"3232327jPPPPPPPPPOPPPPOPP?E eFDLLn<84=A-,**?B7CPPPOPPPPPPPOPPINgqTQu 04DA3.# D8 "Yuy'77'7'!5#35!3#"#"'&5454547632632'&#""#"#"32723277#"33533#3##5##5#535#5335##"#"#"'&54545476323232&#""1"#"3232327x34oocoo<<<<9`C>EGg62H #G9@ <<;<<<<;<<<<<<<<w&'1&'''0'1&#&'.'''1''1'1'''1&1&'&'0'''''"'&'&#"&545654'&&'&'&&5'63231  A@XXII FG^ FF u  HHFE GG II HG HHIHHHHH FF  EF IIJJHI+ H(#UD* CB TTKKII a IH  p  KJ GH JI  LL JJ JJ KK KK KJ{ II HG LJ LMKK + G! # VP#>v>!#"#"#"'&545&5476323232&�"1"#"322123276773232327654'&'&'&'&505476763023632&'&#"#"#"01#"#"#"'&%73232327654'&'&'&'&55476763021632&'&#"#"#"01#"1"#"'&33333353#'!5#53#>l|: "!)7&&''80#;"c; &% )"; ! +"": %& *!< " +#!7%765p48nQQlC,),:<-)  %%    $ '    -    $ '    a.//>W0>HRV[_hqx.4>2"2664&5!%"#"'767#""'66.&547'654'73'!53377#3#''337?'##/##/MuFFuuFEuڸllڸll>:m 8b  eߡ: 9oW@"=T"T="@$M4B4-g_ !'PZ Q OFuuFFuulڸllڸ$$0# #0\##³ںIOB2."HIJK)r:waK) -$-%rvv!)1!5!37#3#''337?#1/#1/FG;<  ,5kx lj2XC_a `ce7Me7 <$1<%1_:327>77#"'&&'&547667632&'&#"N%N." AFb+,YS@_ a$#GCHgyJQ7X-OE&-$`HF "fBV\^V[x sK fC4$$wvq1AUi0125&&5466"#"66765414&654'&#""327&'&#"""#"332654'&'&#3230327>7�"132767654732674'&&=: Udsg (  ) v  !2i 2     w(  F )6iu|oՌhg    %r  44    I  % > N@Rk5636#"'57&'"'!!532$7&&'#"'532>77.".''67"#"'.''>79.).0B$!K9Y%cFUZc+_I<BHn8 %?$Gd;'?7 (1V11Y@7"GIi1G|4 0%+?EDX:.NS1%65:+")'-+G .-JF73-#096- 5*%*E?2W_667632".'&&74667676767"#"'&'&'&'&547676767632323462"7"1"&54767632323032Y8##'"5$ "$  N`]a  )1   +-=@GTKG/#1##1% 4H  4'  0C$   =#    8)1"1##1#$  Ev)32"&&46632>54&&"2"&&46654'&=4&5&'&'&'&'#5#5#5####33335353536767676756554656541"2664&&#2>54&&"2"&&46##676#"132322327'#"'#"'&54543727&'&#"iiiӴiijQj?j׷kkбffаggU,825S;6  0.6K%%:7B/OPjiiӴiiiQj?j׷kkбffаggo_HDbu !' B50-''&',=0(I(iԴhhԴh?jQljjطj gбffа  uT-+  &'@>5/9+(  8Wr  hjjhhԳi?jQljjطj gбffа<U B'(),00+((9 $p 6 >@gq#"&&5462632#&'&'327673#"'&&54544546676632023201#"#"'&476632335#54676"264&32##3"'&554673267"264&#"&.)F)Y~Y   $3'5''7#7#7##7#7##=,&V<76&'&#"'&547>7>&'."#'&&566323362676'&&'&&67632766'.766323267>7632&7>1264&"O%3!(-,$B& =8Z C  5#  #2  -&*     V;1:%1& 3a$ $'  >  2""/  # "/ # L 0"+(((:))3% "3 D>$$[!( 0 )  ";$9 %!,$(3   E38$/(!- 6C 8@ &>* +5$ %; <;' )'  )-  (:)):(;s  )2%563%'&5473 !'&'#  g> ;q A1~x;|+ goB  ?ƀ   $,|<x~1Av+?[j&&553272232767#"#*#"&&553232232767#"#"#"'&".55033232232776746632"& )&#ryyq#&)jqri)&w~ ~v&)'*IJKI*?tS1w~ vStSU?tS1SC" o )( o "%# j ** j # -Y ** Y!8!B8! -!8!!>v#'+/37;?CMYcrvz~64>2"&&%6 &'&'4>32%7'#7#7''''37%'&5463''&5463'7&'"&7676%''77'&"74>&&&667671''7'67&'#&'%#"'32>746632&&'&'&667&'>FuuFFuu {v \v0^ A= ;JJVPH>31AAsQT4DwSU<^?8NOqSXc),% +v64>2"&&7'7'7'7>FuuFFuu-jj--jjӲuFFuuFFu(,mk,',km5!33##5#534< .23.5463232�"1"327'6766=3|PQ9V:ta% 3/@ "DsA" |1J1^pc(14&IR@6<LP>~~ >v&5Hd &'!&'672103267&&#"#"1"&'&#"##""1%0#"267&&#"&'5323267!323732767676767!323732767676#5#"##5#"2233267&&#""1"732>54&#"732767676767654'&'&'&#"##""132323201#"##5| l 3KJ44' 'RW  "R""6b   ++ 8.*   ++ 8.*T #'(T #'(0*4JJ44'   x *+ 8.* * 'QX bR!" $')U=0!G33H"   'Be!0  $!0!0  $! +  + :GH33H"C   $!0!H:  8 + C?G0>BGKOSW[_cgkosw{ #'+/37;CT332767&'&5477632#"'#"&/&54773535353&'#"'32723#'3#5#3#'3#'3#'3#'357353#'3#'3#'3#'3#'3#353#'3#'3#'3#'3#'3#7353#'3#'3#'3#'3#'3#'353#'3#'3#'3#'3#'3#353#'3#'3#'3#'3#'3#'353#'3#'3#'3#'3#'3#'353#'3#'3#'3#'3#'3#353#'3#'3#'3#'3#'3#"264'2327"&46Y-  . " <" 9S(LZkD#$Tx      N M      M      SM      M      SN      N      M      SN         [  ' "9 "BC26 [ZYD W_AAAAAAAAAAAGMMZNN@@@@@@@@@@@MMAAAAAAAAAAAGMMAAAAAAAAAAANN@@@@@@@@@@@MMAAAAAAAAAAANN@@@@@@@@@@@MMAAAAAAAAAAAlMMAAAAAAAAAAA   I>3$5n #'+/37;?CGKOSW[_cgkosw{!(332767&'&5477632#"'##"&/&54773535353454'#0"1!72327232##"'#3272677&#"1454'#0"1!2327232'767!##"'32736767654#"##"#'&''&7!'6767654#"##"#&735#35#35#35#35#35'3#735#35#35#35#35#35'3#535#35#35#35#35#35'3#535#35#35#35#35#35'3#735#35#35#35#35#35'3#35#35#35#35#35#35'3#735#35#35#35#35#35'3#5735#35#35#35#35#35'3#35#35#35#35#35#35'3#2"&4632327"&463%!. 7&'&'"'&'&'&'"#"'32723'&'8O(  )   !w[Bp!CN^-+7 *( #Ii/S-+7'$*(`qO7tiI(*M[_(*1O DD DC9 DC DD9 DD DD8 DC8 DD DD   i   <4 PJjJO 4<   ~<Ij E %*Q # 2  Sq5@5,0 QNN6&! 1-  L|~ 7 6&!(E 18Et[ZL  ">$91 #999999999999D888888888888DDT999999999999DD999999999999D888888888888D999999999999D888888888888DDI999999999999D^999999999999Dn    ,    ;M7C?G0>BGKOSW[_cgkosw{ #'+/37;CW332767&'&5477632#"'#"&/&54773535353&'#"'32723#'3#5#3#'3#'3#'3#'357353#'3#'3#'3#'3#'3#353#'3#'3#'3#'3#'3#7353#'3#'3#'3#'3#'3#'353#'3#'3#'3#'3#'3#353#'3#'3#'3#'3#'3#'353#'3#'3#'3#'3#'3#'353#'3#'3#'3#'3#'3#353#'3#'3#'3#'3#'3#"264'32327"&5466Y-  / ! ;# 9S(MYkD#$Tx      N M      M      SM      M      SN      N      M      SN         [   ' "9 "BC26 [ZYD W_AAAAAAAAAAAGMMZNN@@@@@@@@@@@MMAAAAAAAAAAAGMMAAAAAAAAAAANN@@@@@@@@@@@MMAAAAAAAAAAANN@@@@@@@@@@@MMAAAAAAAAAAAlMMAAAAAAAAAAA  I>3%2k #'+/37;?CGKOSW[_cgkotx|$332767&'&5477632#"'##"&/&54773535353454'#0"1!72327232#""##3272677&#"1454'#0"1!2327232'767!#"#"'232336767654#"#"#'&''&7!'6767654#"#"#&735#35#35#35#35#35'3#5735#35#35#35#35#35'3#35#35#35#35#35#35'3#735#35#35#35#35#35'3#35#35#35#35#35#35'3#735#35#35#35#35#35'3#535#35#35#35#35#35'3#535#35#35#35#35#35'3#'35#35#35#35#35#35'3#2"&4632327"&463%!. 7&'&'"'&'&'&'#"'32323'&'8O(  )  4 2ϊBp!CN^-+7 *( #Ii/S-+7'$*(`qO7tiI(*M[_(*1 DC DD DD8 DD DD DC DC DDd DD   i   <4 PJjJO 4<   ~<Ij E %*P " 2 |}@6,0 PNO6&! 2,  L|~ 7 6&!'E 18Ft[ZK  ">$80 #999999999999DDH888888888888D999999999999D999999999999D_999999999999D888888888888DD888888888888DDT999999999999D>999999999999Do    ,    ;L7<l&4Uj32345632!67632#"'&&'!4'3445632#"&546322 3##5##45&"##"&5467&5467232#"5#?"""7'&  .  ?" 77 44655*'7   $K#,'7 ; >d  D B]55>)7', [  >.9CR^jz.Z##!46633!"###!"&545!0654>3326766>#!"&463!2"&46332#32##"&466##"1"&46303327#!"1"&46303!22##!"&5454633463!2###"&=44106504147554564143577041657747470647706563770?!"!23326554663.+#!'"&1/&'5&''&5'4'5&5'5''54'50'50'54'!0"##"#&545756767662330331167676767676+6!*++ X*,/X1*AVA=/1*AVAI{++c'$22##2+#t  #u   !,/*DX%"  *1[ 5;5:OYM50  R/*N!*"*+&$ +G:6/(.A$n_(.A$&     {  u '3##22$`q"   "h  '.D$ G  LD782m#.'$! 6#) #>d+:P]t#+7>Ne7323327#"'&'0"#"&547&'632&&7#&'&&'&'&&'&'&547%"67632&' &"#"#"'2>766767&554%#"'&5676676654'34'#"'&5676676654'34'327#"&'&54766667'>5763232675'&#"626'&'#&&76%0"1"'&546677&'&'&#"&'672#"&''&5471&67675603''&'4'&&##67&#"67&#"#63676&6/3,B(,A2 BJC9+R+*&/#J- 4m,$-  %% +J ,&32&T5~B,2cTHL ! %@))%#q* ,'/, %@))%#q* ,'/, }  ,!6%      O    /<&3/#=&&6+#/"! &l=  !  q      1) )  &(0 1  %.f 1    15  '!!!B41(@AB"  7,6 & ?&>(-67,6 & ?&>(-! +!    6      s O29*-4)I3 &&  * R  E    #    D#:K`y ;676767654&&547667&'&'&#&'&'&#"6632327#"'&&''&'&'&54767%67#"'&'32323232766654'&'.'2'0&#"6767632#""#"#"'&'&'&547447676707763235##&#"#"32327'2"5466"2'"&&543273535#357###232655#3354#"#"55#3254''&5432J] AY6bF 39 vOS 8]ASw69C?.{E(p69A?_TPkq(,kP _a?A96##30Pz1AU3w"%iK! S?C96zP03##,(_"Ki%"Ls!! "KK%3 US%  :# "$  _(&6Z@\ 69:r*-KE/G(&PwQq%&~D`9[05]tQM ;MFI-+  m [ ve7:96 /-3XC0ME`&%qQp Ovm  `EMwM-/ x( /1C !` ##]hhRD_  '  >w5Xy3232#"##3232767676545454'&'&'&#"#45476763221232#"#"#"&'&54732>7654'654'�"1"#"#67676765654'�#"'67632323201>73-'%-27tX#" !'' C :6A"?5CGT10UHh($@ ('$>?$'    0 " (2=LO KI;0'H/ %5886'  UOA)"%RW#)($_x L@4  @L  J?34?K 8 )! 0(!  >vR463!2#!"&57##!'%#'326554&##"33#"3326554&##73#"3326554&> $  ^=ܩ\g``g`:`  hcqЦ> #6%"&4632!.!>"&'32>773dB\BB.4p;wnW9$@N@$P|Pn*#AN@$.AA]A3 C]K2%**\c51%))xw<Wl632#"'.'&'&547>22233276767654'&'&#"&#"1#"'&'&54766&7>45462#"'&5473276'.67>67672#"'&54CW`% rK4 1D %+#  %4K0 1B= CR_ nUO 2  7&`     -   # ' # &TFU55JK{qJ*% @%?<Pfu&5476760##"'&7&676746#&#""&#"7"50567&66#"5&6&'&#"10#"'&#232"&66'44566767#"'"5476706676&6#"'327632>36'#"&'&67"#"'&547667654'"'&54367>'&&"1"547632'&476666667632&676326326'#"'&''#"'&'4&"'&&547&&'&547&&767632#263&'&667>7632327>762326764.'&543212>4'&'"'#"505>&&'&#"#"5456'&#"#".'&#"&'&6'&&'&6.'&547632&&767>0#"547676"1"54767676%054321'&7&'&62170#&'&'&6&63766762"#"5476676760#"5476767        D) %2)  !         *   ,,&      =,  0 <#(#&"1/" B< #' +5  /i! ' $  1   # &"  58     $:    ( J   #             ;  ,   g   !    | %    >    P &- !  @!  33  ( (?      '%   ,%9!D " 4# &%,^#' ( & 1   7"%  I  F%    ! )!   "E  i        (  I    A  ,      $     R   >2&.G705472213232#"'##"'4&7323276545454&'##&76332376332##&7&544145476323232#"'"'�"#"#"32323276323#"#"#">U<>;=T_9/ @-9KOccO >>=TO;/!,0" # -+"/9ORv:9SQ8;K! -.@j|8SS8;4 -# ./!!/6>v #'+/37;?53%5375355353553753%5353553%5355353%53%53%53JJJJJۊooo8 >!2#!"&546&7!!67##536&547#532PH!//!!//\C )!MC! (+CT&/ /!!//!H!/-5 D09B   !  N+-/.##3667.#32>7!66754&4.'! $TC+( !CM!) <   !   B90D 59 Ep%V;r|%4546676776'&'&#"6767327667&'&5467>766".'&'"".5.'&'&'&6763266%67#0#654'&&7&&6676767667'#"'&'&&6&'&&'632'"'.%6654'7'&>7&#"76'&&'&654'>212654'&'>7772>323&'.'&&6636&&'&32767054'&%7676762&>7654'&667654545&&'&&>32324667&'#"'&#.'&6654&&76676766766766767>76&&>767660#"'&&'&676666.%67>&&67>>766&&&&67'6 z (  .A-;  /#" .!)EN$(DMEo UP&  # Ya  ,#! 2:<*   JA PJB3EMD($NE>. "#/  <-A. 7  y    OD!!xP  AK&J1, `Y#  &PU    "2-: *n# ?/C"c"  s(F(   '1!33F." *(U#"&'&>7454'&'6&'&>7'&'&>7,A<8 ,3:1 g3aw/(/9D$& !.=4d%Md+/C63#'62j%#Ha,;&N>FZiW:, dS׶\]$C"0+?*:]7' ^`Oijg\!9&1J5>fB1UfR˿jY>6" #5Na75%#0327654454'&"13#"'&476200327654454'&"03#"'&476200>soD  E'(O  D'(Oh`a=s   L%%   !K%%v/ !%7'3#%'71'5771'575l\\-l-l;@77@777JJMiM7p 6 6>y %.7B'766327#"&54547&#"&&5467#"''7)#!e;aZ.# fU#"&'&>7454'&'6&'&>7'&'&>7,A<8 ,3:1 g3aw/(/9D$& !.=4d%Md+/C63#'62j%#Ha,;&N>FZiW:, dS׶\]$C"0+?*:]7' ^`Oijg\!9&1J5>fB1UfR˿jY>v '!%!!'77!7!zbz2bJD;/Jb<|b>v !%7!'!!'!2blzJb߽zbзb<߽G 2"&&4663204&&'"3266'#"&&46630327676767454'.'&'.>767&&'&'&67673272654'.#"6#&#"3276#nmmnnmAl[ruussuts pqqppqqpGF204  (Dw*  _b- KN " I_8s )&(9K6y`:9FMEJ  fmݻlnoRl@vtssrtpnpppr   !?      7I74w?Bz/Z:\8TX(DVr 1?Oguv|t0  7 E W h }  3 @ N a  y  I S b k }  %767672323'&'&'&/"'&'&'#""0#%&''"5&#&'&""1367672323&'&'53'&'&'&'&/&'"'&1#&'&##"3376774>56767676767032636767676332!'&&'&#"#3'.'&#"#37676763221767661654'&'0#"&#":7674&&'�#"#"#035303277232327654'&'654&#"&'#"&#"#"&#032767>'&723367654'&#"##"#"'&'"#"36767654&'&#"67654'&#"&'&'#"32236676&'&'.'&#"276676&'&'.'&#"3%1>&'&'&'&##"'32767616367654'&##"##"'&#""#"#"'&#&#""#"'&##22132302127676673'23276767676'&�##"#"'&'&&'&#"#"#0#"'&'&'&'&"1&#"13272321276767323%27672736545'##&&#&##236776545'#"1"1&'&##5#7&''"5&#&'&""1367672323&'&'&'&/5"'"'&&#&#"03670767676676763676767676762233&'&&''&#"#!&'7&#"#367676323&&''&3676&&4&'&332762367>54&54'&77366'&'&>'&&6#""&"236632327632322632#"&547"&&#"327654#"#47654&#"#"54454#"'76545"#"'77'&56"'721270&'45477676'.#>'&77"'&'67674#"32767662'"&&32'"'ɸ"'&#"326327654#"66&477"&&5472767676767654'&#"#"%.76'&'.547632327'"'.6654'&"%7677&'>36326767454'&&"%&&7>6474746567667667632320"##"'&&'&'&'&'&#&'&'&54767676767323776767676676767676676545&'&'.''&'.'&'&'&'&&'&#"01#327>&'.'&76767672321#'&54417767654'&'&'&&'.'&'&#"####""#"&'&'&'&'&545676767676323032331155767654'&'&''&'&'&#"####"#&'.'&'&54566'67676763233#"'&5416767454'4'#5'&'&'&'&'&'&'&'&'&5454747667676676323221216767454'&'&''&'&#&'&'&'&'&'&'&'&5501'72666'.'"66"'&&66&'&&67#"546632'632&"#"4'&#&67720#"&##"#"4'#"#&67632232'&#"1#"54'#"#"545476323.'&>7662&''&'&'&'&54563033232767672321#0###"'&'&'&'232276767276147465676767676726567#"#&'71&'4'&'&&'&54363332327676721200###"'&'&'&'32727676727254767676767676767#"#&'7166767&'7#"'.'32367#0"1"'&'&'322167#"'&76672632320#0"#76#"##"'&>7232232#0"#"#"'6767376766&&73>2367654'#"#70#"&&'"1#"#1"1&'"&&'232327"'&'&##"##&'&#&'&'367&64&5#"#7/&'667.67>267>'.'&723276676676&##"'".#"'"."'"667>3'"#"1"&#"3232327>3:32323232327>76&##"1"'&#"#"#0#"1"'�"123232763632233216763#"#"'&'&'&#"##"#""#"#"'&'&5&'&'&54723032367&&7632'727675'&'&'&#"7676#"'&&656'&567"327676545&'&'&&'&327>''&766"&'&6672%32766763267667>#"'&&'&#"&'.'&56767#&'32767#&'67&'662&547&&65041>7666'&#"67#"'&'64.'&.7632&'32767%#"1"42#"542%32765&'&#""#"'76&&#""42%&56"'&'#"'&'#"#&'#"#"#01"'0#"'#"&1"#"'"1"'&'0#"'&'&'&'&547633267667321247632332633267326767676326703630232476323232630326?63032454766#"'454766'0766466#%54#"&&776'&#654'&.5'>.7676'&#&">'&"&?&4&&'&'076#&'67&#"#0"""&552>'&#"#"'&""66?6763323022>76737767327672327>?3677>'D'[\4? AB"  %B@+  1^_C =  )         E#   ,!' ) "49-8$! `'!,   (,#($ #  l$%,$>'z/ *>& 1:,) !*g r.!)>& 0:-( !)e ( (:-O3  m~ G   6;++ 36.-   ,$)'% D  #  #"        ($+(&  '/      KT{%B@+  1^_C / !!=F3   !@A&  p^]-<`^1-]^<D ( * $  M    /       +/     ;L, ( Ddj%    4  F9-8$! 2%"3$ &2 $8%[ ' )7"  2#\! ,!#  !'- %0-    !/$  +)/ O*! 2" / # J%     'N , !  5  1/4     4  25 (   ^| HbJ"%G0.:*( 0 .0))    .  *,,)-    )$  ,/30   ,2_.,  ,., !#)d'.   '8\90'6:E)Q9@ZERPF/$J3 /,$   ' mQ4*$ .&5;% %'"#&%&%       #)&'""(& %'""#*&$      ")#*""'' 49./#GCT#0D"2 *+pX&,./fb ),/2!* .1 */1 ,Y[) 3 +.e  .+ -,[R,.*  ,%}*C   #4A>    p &$(;!   &4  & ,) w,,  &#"   */ '5: *) 370- ))51  !,(   "&  ++1 (0,Y77* !%  *X $ ?,*. ')  #0 )Y $&     l " . #>3* ,( ""**    W"# >""Q !4 +0 $,! #3 !#'# =0 $r O^ *,*(&S)@" 4 $    #+0       (    ! %  "      u  "           )!      !    "        7/%$    , - 4 d  6 B"     F !9!1&"? (/ %(  ' *4    !   & "\  :+.%-> # /'3$!% ! $ "$3  1-.r " ,N508;(W /1.o " ,N508;(X*=N)E9K# A;K "    ?."0$  E  =  +!!$(  8( 0  !  II  6 B/   2;61;B   %'     ! ,$$-R)$ v"   '    #     1  ;9 # %-> >*=  $$>,0#!     164 ;:.*$9)2#  !  W3&4,# !\5)9$/    !J") ; ( $*&+   )5 '@+%.   46          53     % 41:?P&5QjH&&'J4|    +/    '0   %*   %-      3,#  ,        +0/)! 0  4==/M`v+.7*& *'pe  \h   Y3f  $%  d     !                  K@ -5?L 6- :A '   P   #>, a          " B   !g    rT   L 9,  1%89, 1&T  L  ' (7m$ " > $6    /( "% 0#  .  *%       NQ Zc X0,^U  !     "    # + " :   !      1B                =5 2 1(  8       ">.s    % *-  2  B ( /8  D3) + -.   - "#! " H   svu '75%57%%5'%@eeccde+Iv !&*6Od!!'5'!!!!''3'73%#35!!!55!%#3#53#3763232321#"#"##730126545454"1"254'.54546321232&'"0#"#"'>'>.'>77'35##5335#35##5335#35##5335#5353v$StSs}'+GE)'$  U    >HEZI   GXDGIFZIs!! ,!!ߵO-[ 4 0N  L      HYLAIFYF  H\GJt!! !>v 757!'!~9SڤS>6"%07!!#3067'"&5!654'654'366323232>lxZ9-" nAa]@sB,*<6aH0,6;8Ek eN{+:;*>6" !,7#3067'"&5!654'654'366323232ȊxZ9-" nAa]@sB,*<6aH0,6;8Ek eN{+:;*Kv %%7'!!37'#]L]8yf&tts,KزSS66?##0#!0#"'&4767!%7'57 r    O  P ,ԗeeee>w)-159=I232#"#!%%!3#3#3#3#3#53535353537'7' APPPPPPOONNyFKA00FQQC1+oZPKlt?D)E(E)D)E;EEnEEmEEmDDnEEy~yt > $0:DUkrv#!"&5'%5463!!72##2##32##32###!2#!"&'>376733'7##&''#2!5!!dFZF%Z  Ϧu X9/-9LJ5*(9HF@ߦE==˦N O wwt  UUzxM  Myjrwv$^l777'264&"&#"326'77327032"#"#"''&'"##"''&54567762'7633'&5467632#"&|6JCa1F11F`'$ '=0 :G L0IN5 (7 &$%0t+~!xA..A.,!1Mu  ] $/-91a~_'" ( %"#>"&G%676676766#'577'733&'&0'&&'46656 :28d-*`Y,<.67-7 W_(o1nXpQ 36W4J<,C+>) MEj%-5" /8l F<I1B `'=/ " 20>v463!2#!"&5%'7''>))))'ee'))))k!!v !''%7%%7'  b6vUP6@U_S|'ʉ>$ N )J\h7663237632  !!%"'%672321&#"#'&#"'&#"762%'&'&#"'&'"#"%"'%52"'%67632767632767232264&"3"&465"&55'03767654&'####3232m  q, -aE gQ +p  mgSP ,p  &T m  p+ QguttR):;R:: /(b   <    T D#EҢg U  Oh T    U gbuutc:()::R; / - (    w3 %+8EQ766324>32'5762'77632%&&#"27%"'%632%"'%72l  ok nJQ,RSO, P`PX#S !   "   % *=S QU _  g W %+8EQX^dh766324>32'5762'77632%&&#"27%"'%632%"'%72577575'l  ok nJQ,RSO, P`PX#S !   "  `I % *=S QU _  goXZ9XWXXW?XWXXWu,"V>.!630215&#"#""12326767735#67676>l7a $  7`!T  VKn1*%+X(& p1*"Eo Fv?Hfp2##"''&##"##"50517633'"57'547632##"556.#"#!#53532554##"5543325'4##"332554#5!!!"554##"332574#3327743323325'&##"#e4`/[I&4.(43 8r$..3S To+!T!  )* Q^^<‘*/ L(N#>.R## @> L /B3"#&732323#&&'4&632#"&5476232#"'&&546dd~iA;:9 w+ )#3 (.L(# (0 2##b*  ("1 vU  $(,0<5#75#'3'75#75#3'5!3!55#3'5#3'3'#53'#35`\b94&JKDNC=Cwb.(BO> 5\- 3?CJNRVY67!"#66%3""##&&'3#6623!33274453223!3"##"'445!%73'11%3717#'7 "$O" #"iM m )mI"]x :txvvWI%'##(!)!)*#vv,,VUU*[r= 9trvvWI$|O %7!'11%!717!'7i@cS)fD@bR(>"6*9%'#53232765&''#323#5767673337536"#"'&545454767675654'&#"#"#"#"'&541676323232677#"1"'&'#RTx     ! $" *K& "7:    #(1&" ,, +=C*) $d  *} ?&    +o><  >vI!!!!!!!!327276765455##"'&&577357#54'&"12&#"230212#"#327354&#&'&'&5475723235''"#"#&54745676323632765'#"#"#&5454'"2#0#"76&'&'"'&545''&'&2#"''5&5476!!55!!JJffw    ;  #""m   D   ! ooJffJ##1 GP 43-"NA IK fgE  A  BAo_Y! R  ;  4~wj$0<YSxO!Bj7_~4547613676554320'&''&67#"'"'4&5&'0&1&5455445101%&'&547>367054'&'&54767210#"/&547537673672121232763132011#"#"'&676735632110"#"'&5&547654'014'5#&547156343326736767676767212100#1#"'041&'&'45476'4547676303321"'0#5&'&56767673477630107#"#"'5&5476574575632#3#"1"'&'#"#0"#4"&"1&545535674?67767356545675673763230332763122#1##050"55451454'5&66776767>32132323276320#0#1"'"&#�#"1#0%#"=4'&54774107632###5#""1"'&'#&54715633276323303276767676767047677632#1"0#0##"547673#"'5&'&5476'&'#&547534736767516320107"''&'&''&'54715632%5#0#"'&'55#45147411632037030#0"1"#1"'&'1""##"'&''514#5#0#5##41#1###01#"''&50545675057654'&#&547351065676326767630412%0#0#"'&'50'&54730336776727676767766322327277676771632101#"'&5#44115013167674131563#"#"55167010115163321     Bc %6  % X.3 27k)+0  +60%' #       0 ( ""  &"%  . B   H    . .4# G :)  j( 6  . 3I )+8j  M    P !/3"6     $  #u   C   ) !  N6fJ (& 325"$=Nm;       V       0 )3. r )04#M- ,6$"  *!# &%0*@   T g0 'A.5J9 P= 5C .-)7 h        # &"  "#% %$ p tvt;w&X>o%eAo._T%#/ 55? ;  ##+'#/ 55? ; ##+/=75?37;###/ =7573?33 7=75? ;3 +'#'#'71'=?37313+####'#'#5/5/5'5''55'5'= 7575577575?5?5737;3###/=377;'#1+'#/5'=15131333%#5'=? ;13###+'#/5/5''5'5'5'5'55'= 75575757575775?57377331;3##+/=757337;%''5'=?3333##'##''+/ =?5?;3 #'55'575? 37333+'##+/=7577;#1'#5/ =13533 17135;3311+/5#5#5#1+'#/ 5'=153133317#1#/ 5'5715133133 1135137;33111#+/ 1#51557575?37;111 ####5'#151=? 5353313 #1#1###+'##15151573773?;3131##'##/ 5'515351;3 17#1#'5#/5'5571513133#153137;33111+/ 1'=75?3'#'#''##/575? /5'575?;3+/ 55?333#@"b            xL          81 7 7. 7     l  a   ~  #>)ELn  "D           c8        E  L    >Q '+%7'7'7333733#3##7##7#737#7337#ee+ee+XXXXWWXXXXXWWX9WXBtBXXXWXWXXXXWXWX>W)33533#3##5##5#535#5335#%#3##!SVUVVVVVVUVUUUUVUUĈ]WUUUVUVVVVVVUVU>nV>7%57'5>ЕQ,ؔa D9 ",6RV'77'7'!5#35!3##!#3#7#3##;3533#3##5##5#535#5335#x34oocoo;;;;?Fg^;:;;;;;;:;;;;;;:;&I~~dccLLdc ;;;veK;;;;;;::::;;;v;>'53573#3573#3#'##В廉#JuZσJFp av #9CNW%%%%%%%%%5%'''4'6545454'&#"76654'5760576%'-i~f*pw +s[)vun!#mP Y$ *2G  %Ϙrmn֋))反хqܣQ1TG1dPdy@,3 g]7!V^P+`k[ 3d3 }~: >v$2Mhv'&47762'&4776312"66741'1#!"#"''677>!"#"''712676766531%!277650570''&670#"''&5056771667'&54147763!2'&50547767!"677Ayx yB-x+Kz   U%%+   * /+.R  5@B=EUM9|6~r+E% .     R /@Kyېy{N+y)S{{  :##+   &++[  D:EK>Gb6U ,vzm$8$+#6$ $  $  RH H::u+4L'&477620#!"#"''677>0''&671'&5454763!2&& {   V&&1."KEWO:!/   ()&  {   <##1.&)<>Hc7#7% )  >v"135:3.'|!I6'3>I\J|">I\@w~1J[.Wo"3672326767212"#"'&'#"'&'&'&'&'&'&'&5476767&'&'&'&'0547676763267&'&5476767672326767632#1#"#&'&'&0&'&'.'&'&'&''&'&'&'&'&5476767'&'&'&&'&'5&'&'&54767676723267&'&7676767"0'63367673667&'67&'&'&'&'665&'67&'67&54767"'6672676767367'623670676716767&''67654'#"'"'"&#&'6676676767&'&&'&1&&'267676676232767673676545014'&'&'&'676767&'&'&&1&&'&'&'&&'&'44&530&1&'&#"1&'&"2723"#3023676676767'.'&""1'51"&'671#"'&'667332767675676545676767&'4456'&'&'67&'67&'&'4776711&'&'&'67""'#&#66736767665367&&'&'&'7'&'&'&'&5476727&'#66730'63207&''&'167232&#"#&'&"&'&'&54767676723267&'&766.'&'&'&&'&'45367667&5456623#"#&'&'&'&'&'&'&'&'&'&5476767676767553276767654'&'0&1&5#0#"327263#"'&'&54456763232765456521454'&'&#"#"#"#&546303#"'&'&'67272321#"'&'&#"&'&'66722301#"''6323276767#"'&'&#"'45476#"'&&'45327667654''&546303#"'&'327#"'&'&6732766767#"'&'676723276767441#*'&'&'&'&'&5327"##"'&'7v45  .+ #  #%%  # 34 +). $  %%   $"' "& *+  82 "  -,    $    -     "   $""/ &j    ! ")'3,  !D#    7-   $    +-   # $& (&$!   "%   )'# , ('^:C    #D!  ,3')" !   7*  {L /( n*+  (&$!   "%     $"!0 &a## %&72 #+ L&4,  !D"     #)9  V9'*]>4uU ! %$ 2 5*4 1# ?YbE Fb Y?   ,,%; )( gW@B l%,>- $0 3*5 5=]*'9V &BW  Ep   )* 2 $"2 ,% BAI=-4 WB& %, !  &(   "). <9$ #0   ,/4  ! ).  *)0.'9 *,  '  27  0.          " =: 0!-. %    5? *(   J,      /0  62   (  *  ,*   ;   !/2 "       [ b2  ,J   (* ?5 '&+##!DY         <   !.2  =: 0!-. '( '  '          J,  '+ *) U9P 9W G E\   kE >  $D  P}/+  "(B DO  O05F       2 3!  Gu :!-    >-+ "%#"&''733'32#7'#732#"''. v@ZL~ wA]N%/~ (-l$mF>v/5N#366327&&#"66%".4>>7#366327&&#"LۖaD[Fy(/0TzsQ5//zEwuFuuFFue5mLzFۖaD[Fy(/0Tz,>GmhSh@7)ALtcvEz/.5 uwYuFFuuFVaREz`P>GmhRi@7)ALt}wk7fh'7%#"'&'&''&'&'4&5223327676767677.67#"'&'&&'6676672232'%&'.766766&&676677%BJBk!.!  " ?#   "NSS*\@:(*d[-VcE=QNJD6Vo2x6*9 mS=6G&eb*q"  ST(8PJ[! _3.  + !$  "(d0YP1#'ajL@P=*3"W`j.h>+&1g#S,XY  !h@D_q>v'!!.#">37>ll !;-*%$(8q\6^vwd>BceS H/@KG;$?dxu_4>v:d&"632632"&547'"&546675&&547'2764'632632"&547'"&546675&&547'.Oe 'a ''8'Z'8'  c~.|e 'a ''8'Z'8'  cS~Oe( a'7'' [ ''" c/|//e( a'7'' [ ''" c? "!3#&54776231#62B}X 1aa1 Xag4 *  + >v:g&"632632"&547'"&546675&&547'2764'32654&&'5264&#"'654&#"'.Oe 'a ''8'Z'8'  c~.|c '  Z'8'' a' eS~Oe( a'7'' [ ''" c/|/c ""  ' [ ''7'a (e?i   ,27>CHLQV[_cinty~#'+/37>LPX^x"$&,-/135;<DFGMOSUZ\^`dhlqsv$|~\^bhuwx|048<?y|     " ' O R X s y z }      $ 9 = @ D F H \ b h l o q |  #&*.159?CGMSWZ^beilosw{  62"6#07732332333333333213232773273232?3232772327323273032?1273032730327703277&'&'"&#5&'&#"&'323&&'.'&'&'�"1"&#*&'#&&'&'.5323277032?212?321273212772127#22"'&&'&%2231#000167667676767&#"&'&&'&''2327&'&#'#&'&/#&'4576307373770327732763233232?67576737767677576763217326%736#0!63236&6770547!62#"&66"&634720"'.'&'&2&42#"6&6&%36&6&!6&&&&&&67#"&6&637%6&6&2&!367676323"'7"#"6&6#0%77363267677267301#"1&&'"1"#"14766767"#"'&#&'&'.'&#"01670#4'#"'"'66767>7667&'&'&"'&'&'&'&'&'&'&&'&'623&'23&'2327663232366326676767'&'&&'&'#"#&'>75&'676766'&666&76&6667%46767%6'&67#%6'6'&6'&66%6#666'61267>673747#>347676676505445'#"'&&'232767676&#6'&6'67677635#6#34%7!6#"3%6#"2"#"'&&'&'.'&'7"'&##&'"#&&'#"&&#&'5&&34547%673454733%54147%6'&7%414737673701%3773677%6&7!&''&&'&''&'&'6"&76737%7%73766#"7'76#"##3377#3!3'!6%737567'&676'&6'&63233"#"'67"434%74&73&#"#.'".#5&'465"#"'&'&'&'&'&'&&'"&'"#"#&''&'&&'5"#"'&7677012303233222123333#67&'2327"&#&'&'&#"0#&#"1"#####"#"0#"'&&'&'67267674532367232766763212212732767'675#67675"%367672126766'67>3615'67675676'6'&!367%3'6'&3!737!#'46#"!6'%6#"723#"#.'&'4&67%76#0#76322367#'54'#&&'&'&#"#.#&&'6766%67!22323&'7767676774665"&&''#&''&'&'&'5&'5'%36#"!676#"76%336#"77!36#"7'6'37%6#"76%7336#"767760#"346#"##"'&'%73232&'7663&4'#454%3676'6763037#31"36727##'�##'"1"'&'#'0#"''#'#'#7377767671'&54767'&7737#!2#"#>7&5472327667#"#467##3#!'&7!73!'3767373477#373612%736%3"!630323"&'&'&&'73237676326332362326523232'&'&'&'&'&'"#&'&'&&'.'&&'67&'&'6676674'6'&6'&636'&%67>7777763237&'&67332767#"#'"&27672767676767727&''2323274'7454'67454574'&'&#"#77&&'&&'&#"17&'&&'&&'632122333232323233221232212323330323233335"#"##0####"#"#'�##'&'#'#''"''0#"''&'#/#'#'0#"''#'#'#/"''#/#'#/0#"''"/#'#'#'#'&''#/"''#/&'&#&''1"''#'0#"/&#"""#4#"###"14&#####"1"##0#"0#"#0#"#0#"#'#'"'&54454537673?6?67767276761"#"'674%767&'&'&&777372&'.7353#4%7366732367&'6'737377#472327674&763237232'77#3674777'761"#"'676777#7367'7%#'76'73737%232"#&'.'.'"&&63217''337##77777#77'#76'777#7#3'7%'777%733%6323'%336323'6323%'3#'77##77#3#'7##77#6'#3#3#3773#77##76'&##3#6"#'#3#3#3''3#376323'#37#3737##672"#"77#'33#337#3'37'03277'23#"    56         &( 8       !    $; .i Vlq@u  %)   OO  (L*  > F     2   0 2            !   %    65   ! 1 b; 8r npc       E;    i L! M       .  " /u 8E2 t~G)FP %     b~sH[()+I[T?Z=   B!, R  ?           %t    +     0    2   1  !   PC]+<*A U5  o    <$z       !ePEMpA"w  s C  >`q   n t     P*C    R              +*d < +  :c'        % a),   Q-           E      > 6C    w 8}   a  *"  >HQ;!HQ<  sZ|i"=8UN ?-! !TqiaJH ,'      `  h &    7          T   &             7          %       VR      h    *7   \!  b   "1 4 &~  E B         d tre    !             :1  O             7   A            0L  >kBHv7"#"&'3277#&&54662''&&663021032#0#%3#533#'#""1&&54745463221232#&&#"1"32>7#%"32654'654&36330#"'##72654.#"7#"#".5474632#3212667'&##54#"#5363232'$%se#7=c6\ZZ\5c@1 %f( , .. fQs"|D,- $"  / &""!!C  ,     L    U8  8Y00Y8 $ S*.  .!KW, .  8 V""""+45 ,    " <@8g F>v. &546!2323#5!#"#"&546632232&#"3: 5",9[ǨVZ; "& feӌN!2-(>v"Fdhl!"&547630327632#1"''6"''&546326312632#1"''"'#1"'&5463276312!!!PF  W)t h2 2T  , vF  3 z9O :  'Ll]C %Yp"776332#"7763!20#"776332#%&'&'&767#"767633'&&'&7676&'&'&7676&'&&76 ; 6')>?G4(E1   .MxjP$/ GKwiN6dD? JGzsRTa!U0  R1b$98R?A ).U*= 6,  K?G4(E1   .MxjP$/ GKwiN6dD? JGzsRTa!U0  R1b$98R?A ).U*= 6,  K;%7#.54732765532655;3376557327765#&'676767&'67632676754&#"#54&#'1"'54''"#"'54''%4&#"265"&4620326675&&"32174632"&5264&"p*rttr2QL n8 9n MQ"-(&8==8&(-":d WHIW d:2#(2G2"/!!/"  t2$  2G2!/!!/!Bn??nB48: == :8#%'8/ /1 (,,( 00 /8'% 36 8 ;; 8 63  m$2(#22#""/!!X O  O ]$2#22#""/!!]C %Yp"776332#"7763!20#"776332#%&'&'&767#"767633'&&'&7676&'&'&7676&'&&76 ; 6')>?G4(E1   .MxjP$/ GKwiN6dD? JGzsRTa!U0  R1b$98R?A ).U*= 6,  K7>56545&#""'&55&'676323370#"376767456505"67"543767632#0&766#&&'4547632'"7&!6'7632'667454'&#".'6#&'&''7673'277654&'"5&'&'&543210#%'&"'&&>76"'&'#"'&4>767536#'233276767.3"5567676320&6'6&&'.5&545232&'&'&'45&545536320'76553656'&#"#&'0##&'&543222'&&'&&6656.67>7"#.'&&6767454'&547676723>766&'&'&5654'.&66&'#"'&&'454>7632#0'327654'&&#"&6&&'"1"'&'&547632'32766541454&'&#"'2>7654#&'54547475'6767654'&5''46632#"&624462"62474547"'&54?'7&54765'7.#&&'&546&'&'"&'&547677'27654'&'667634'&6&       3 ;2A#5*4*##=!88    (    =    4%    ;        A     +  5 *?B4  -3?:Bt 0EC- 0B;IGM)?D5"?K<".3(& --4$=QF!3C=,/' J[P *#O"() NZ+/'M!*S('  $       l (  @     [ A"3 @  ' %#'       6     &6)#"h$  !c " h&f`# G  .  >       7         % % %        8    '-"DE=O;5/&*.1T9nH7& 01&'B@M".#di= =Dh&$2X +@B&&01 '7hc"-*160C!# C  -363(< ! $#-< *!! u   +          @    5  +   9   * ]C %Yp"776332#"7763!20#"776332#%&'&'&767#"767633'&&'&7676&'&'&7676&'&&76 ; 6')>?G4(E1   .MxjP$/ GKwiN6dD? JGzsRTa!U0  R1b$98R?A ).U*= 6,  K3!33274453227!3##"'445!'"776;2"776330320#"776332#%&'&'&1767#"7676331'&'&7676&'&'&7676"7&'.#""3276 ""M" "qFk?eB  dWc/  &(,", S  0KC11,.KB1"?+' /,MH34 <+= c  =3&#!' '!'(PNst++cNN*||  '  (  X  4&" #/%@ &#=D:> !&%!2F<; "$=U=,   = "  ]C %Yp"776332#"7763!20#"776332#%&'&'&767#"767633'&&'&7676&'&'&7676&'&&76 ; 6')>?G4(E1   .MxjP$/ GKwiN6dD? JGzsRTa!U0  R1b$98R?A ).U*= 6,  K7#"33'&&'&76766267676'&'&'&&547676 dG  6NiwKG /$PjxM.   1E(4G?>J* aEaTRszGJ ?D R89$b <*?@ <%5b]m.P<f?G4(E1   .MxjP$/ GKwiN6dD? JGzsRTa!U0  R1b$98R?A ).U*= 6,  K67654&#"#"''&475632#'3>767454'&#"632.'&'&#"32773767632#"''36623662'41462: *8* : *9*<  #T !(P?F/?*+-% +:=,1  98&*$20"%5 'NX]T5 )#%* !/ N ;9L9:8M9-) %% %% -/JGb< $ % (,3Hwq 0O0 3F@kk<,$!5K  ,,\  !O8[AG+*%22%%33%F  mvFQ_ku%#"'#0#"1"&547'#"&547675.546327&5462632!67#"''012615&&54%0&7&5!632.'#/&547'&&''&6675&''77>7&&6677623>6''3276654'&&#"S(.!#-  -  .' .  .@." .'  ޵"޵!r(!#2  7$#/#$2  5$# /#$ . . - .g .. h . -  . h ..  h. .6gThh* sh *hg8$#  2 , 1  2 , 1d , -nvz #1?M[gs7'5!%7%7'%7%3!3'%#"&4632#"&4632&5462#"'&5462#"'"&462#"&462#!f sf$fcf fW&H&7<- .. ' . .. ' .@.'-o .A.(- ..@.. ..@.. Gl%%  kbb'.@. -h(.A. -!--!- 'h .. - '.A..  .-A..  -f,v #'+/37;?CGKOSW[_cgkosw{ !2=ES[_eiqu}3Kiu '?Rf|);Y!!!!5!5!5!5!5!5!5!!3733333333%5!%5!%5!%5!%5!%5!%5!%5!%5!%5!%5!%5!%5!%5!%5!%5!%5!%5!3#3#3#3#3#3#3#3#3#3#3#3#3#3#3"&'4767232"7#66'&'�#""#"'&'4>76"7#3232767&&#"#"'&'&67232#"'327654'&#"#%&547''6'454'&'7654#73&'7'7654'7'7&'77'"#"'&'4767232�"132367"72&'654'6#"#&'&'67'632"&54666654'&&#"327'&&'676767'7'7#"''7632"#"'&'&547623276541&'&'""7632"1"'&'&5476"2321274541&'&'�''676557''676541776'&'&54543264'&'&'&'�''66545766'&'&5367454'&'&'&'&66'&'&567454'&&'&'''6764'766'&'&'67454'&&'&'"'&'&547632"1"3327674'&7#"'&'&5476320#"322367674'&'7#76767#"'&'&547676320#"032767670545&'776767#"'&5&547676300#"23276767674545&'776767'7767670&'054767"1"276767674545&``Wllllllg { {|||illlllllllllllllll&))J))J))J)) K * * .6K'*2.')#2)!>S;$.((#19P()3)<%(":(.')L9.((*2f!%+$%*;pmy%#556#%: ##Pr$"ON "$.')()3! ("% !H*,  Z#)" QsQ!<#8PP8(C99          &              J !!!B ! B ! ! B!!B!!B!! h5'//%$).%6)/( %/); '.^ )<  2oke\ ^ins ln#  }    - ~//   $8:%5HJ HF;8:$ "*9RR9,#<#)"!OqO!$ T # # "~      (- =3      =@          %&SS      + -        6&    D2       R>eC     >vs".4>2'2654'73264&"&'326654&"�&&'56654&"654&"327'654&"327%>54&&"326212"&&466MuFFuuFFu,X++>,X ",=,$$,>+""",>,, \,>++ ["2%5mܻmmnSM44:fΰffFuuFFuu ,<,=,,<#",,&* *,,))6)%%,,>,=,,=,="F%Zi7nmmܻm?;54Mgffΰf>v ASa232"#"&&'45467>'45467$23221232"#".544564633232#"##"&5462#.50"#"&'454667232V.$LjL#+?mG&*?-t-,XuEX.!s .. s!..A--! *BpH'*.uv,w*<}oX5",5Qa.-- u,-EuX &!..A..! ..A. o,4Qd3+,vv+>vg/]j q4767&&'&&76&'&'4'&'6327663267665476763276676276727632"'&'"7676'&&'&547654'&'''''&''6%66'&'&'&'&17676'&32%66'&'4#"&&'&676'&5476&#"7676"6776%6'&&'&36'&6#"&'&'"76'654'&'"''&66766&#"#&'&"'&'7654'&'&5727454'&632#"654'&&'&54777676'&&63#"%7#"2%7'&4%676254&"767766&'&'&#"#'&76767"'&'&'&'"#"&'&76320&'45476762305476767736674323276762327676611327672#&'&&'&"  r J; (!4-  P#&H  PL 8O   "   5       (  !L2  & H)           \   r   14#+,#*/  CE  %0'   &! 3 L('9%            /n7: )- Q Iq 5++! 60 p     6?: A"<< ,@  = # % )6<\-_k]y)C5520! 1$ hWa#%(+?%k8J(E18RA '? )2&(" )&ME/!" %A0--  $"   *;<#:*-6\"=Z     -#"%,'36 2 `   ' 8% +"y    &9 U R 8 *l8{      / c'>So8bp )}74767"1"&'&&76&'&'&'&'6327663763232767677676327632276323276323#"'&'"7676'&&'&676'&''#"''#"'&#"''676%66'&'&&'&676'&32%66'&'4#"#"&'&676'&676&#"676&326776%6'&&'&6'&3#"&'&'4#"76'6'&&&&'&676'&#"&'&"'&&327654'&#"'&5467454'&632&654'&#"32'&5474326'&77#&%7#"2%7'&#"0%676725&'763267632&'&#"#"1&7676703#"#"1"#&'&'&#"#"&''6320&'47672233#"16767677321265432327676232767630327672#&'&'&'&#"15"   6<J;    #   P$%I  QJ 8M           ( Q2  ( , H #              Y ( v         14#+  %(/  &'" , 1'   $# 3L('8 $            MS V6   $8$%%14)  ! S GL      !- 04/C<    4+0  5*#) "  6']  & #   @        ./     ,       <;6;? T*@ (  ,       @ 6v8N\jq #-<La%IYv$?J&:&4>7626727#"#.#"".'.6767654&&'&&546'7&'&'2376767"#'""&&7765454677#"'.54632&'.&&'&'&56320'&766&&''12>'4.'''67'6".'''7#".''&'66767676'&5476676545'7&''&6766#"7'6763267632&'67632.676676&''676542>777#"&57&'&5454&7.'&547%2327#"'&3032".'&'32&47&'.'.4&750&6674547677&32767454'6766'.'#"'&54732#"#&'.477>3&327676#"'6747706526'6"#"'65'47&'632327#0#654'&'.'&'&547327&232#1"'&'&54763235327632&''&'0#"'&327654'#01"'&'&54763233232"'&227#"#"#"'&&'&&'&547674'&'.505476767654'&5476767054765454'&''763032667&5467067663676321212166763267&#"654547665"667&'&#7>3&'&#"63263676763233232&'27&5456767>5454''&'&'&5470756545&'&#"#767&'&#""#"#"'&'&'"#"16567033021&54767&&'&'&'23267654'&#"##"#"30327671232765367654#LfmS2" %'7-(   ( 5.,'  a 5.,'  \*]    R  2 # B /Z&$ -  h H.1.J|X  @  h  j*>  Z  2 / ; "  -  pO-x1.J|   T  H  8$     &  8 /  &     # % . 4!   P4A    H-   0!L  @ -H  ( A,   2s4', =&,88,&= ,'#  !-4!<  4-!  #   (8  r  F(  4 #  #    <2 #8:?@:9# 2<   ! *H s 3O  O3 !h G*  CG   +RO   06. +-# + ? , , av     &?(     $,)$,# "&5 e &< , \D ( &&)   A. %   ` ('  R,$),"   (  %#a &<  ,U')&&   D  _   ]  '     #6 , '  6# %  , ,    "" ,?Z   GA  $#  w 9   !   & g 9  !     %5!3 #-""-# 3!5%-55      .+  1 .      55  N      &&  :; ONJJNR ;; &   "*   ) 55 )  *"     $(_9?0 0?V($     .v%332767#"&&5''326767#"'"567766767654.#"4745614554'&#""#"&554677677>767654'&""1"547>7654�#"#"&547>7656554&&"#"031276767673276767013032767673103326767656?676677>77>767633223032767677"&&''23032767667102766747657667766747>514'&'"#"##"&'&&56767676767632276545&'&'&#"'3232232&2"&44"?6"5075?-2 57      &HMH'      BBCttt/i b&/ *  +    I                     @ sn hdwUABZ_j#"'6323%0#"'&'#&50547&'&'&'&'4&'#"#&'&'&'&'&56?66?"51&/&50567.'054721267&'##"#"'"'"&#&'5506572617676767672322363#167767667'&677677676723221301"##"&1"&#&'&''&'"&#776323&#&'01'&''23'&'&&5&'4#%67#"#"0132323232367667&&'00677%654'&'&'&'&55&'&'4657&'&#"1"23276756767#"'45476767676767&'"#"5"477&&/&&'06743276767667#0#&'&'&47676763237674&1'&&'&'6746501"'6?67454'"#"'&'20##&""'32776545&'&'&/&'&&5050547767676323332367670367667&'&5457476;I7a/5  !&       '('S    $  &06    Z   '  .     :0    !  )K &  )1C  44)  '')             `RI       U  +T/s     Hm ,$  U87Q7VYX   M4' "*-      >  !    :"  $J   '   o@ AP*%   . $-8 *) %LM  '+    % %H76G$ >  < 2 #    d  B3            >&3s%23267632327654'&#"&'4547632#"#&'&&'&'""##"'&'&545454763232654'&#"6767623267632327654'&#"&'4547632#"#&'&&'&'""##"'&'&545454763232654'&#"67676.$ )+"#     '/0,d$d+0(    */(2.$ )+"     '/0,d$37+0(    */)' "    $/  /    (#:'      %/      (#>s23267632327654'&#"&'4547632#"#&'&&'&'""##"'&'&54545476323654'&#"67676.$ )+"#     '/0,d$d+0(    */)& "    %/  0    (# > 7BTlz!$%767%>6767&#"67'>>767&"67&7%&'&54567%3632&547&#"6776323327&'"&&132721##"'676326767&&67'676324767&715474&&11674&&1bW!  .>= <3")#71/%!4Z-  +.h>ݼTYuъ) a 333#'%'3%'!>Λ3ΛWEEX 5555gggg>a 333#'%'3%'!>Λ3ΛWEEX 5555gggg>  %)-159=AEIMQU1#3#'3#7#7#731#53#57'%!'%%7!%31%##'137'571!15'#"'.'&'&#""#&'&&#"#"#&'&'77673767763232767676.@||]_;5Y5Y5dK%@|SCQQCQzQCCCQQQQQQQQ  -".1  3-'55 0'  2SSSxx]0xxxxQQQCQ{PBP4QQQQPPPPCC  # ' ( * &#-,$ ' !   >v !)3=GOW_gow !)1;CKS[cks{(0:DNV^fnv~(0:BLV`hpx&17?IQ[eow'/7?IQ[cmw!)3<GPY_is{ !)19AIQYais{$-6?HQZep{    # + 3 ; C M U ] e m u }    # , 5 > F N V ^ f n v ~     ) 3 ; C K U _ g o w     & / 8 C N Y b k t }    ! ) 3 = G Q [ e q { &/8AJT^hqz#,2<FNV^fnv~ !+5?IS]ir} &0:DNXcny'1;EPZdnx  )2=HQZclu~ %-5=EMU]emu}#,5?IS\epy&08@HPX`hpx!,5@IR[dnx&1<ENV_iqy '/7?HQZeny",6@JT_hs|!!!!!5!!5!!5!24'!24'#3#24##3#324+3#324+3#324+3#24'#3#24##3#24##3#24##3#24'#3#24##3#24'#3#24'#3#24'#3#24'#3#24##3#24##3#24##3#24'#3#324+3#24'#3#24'#"3#24##3#24'#3#24'#3#24'#3#24'#3#24'#3#24'#3#24'#3#24'#3#24##3#24'#3#24'#3#324+3#24'#3#24'#3#24'#3#24'#3#24'#3#24'#3#24'#3#24'#3#24'#3#24'#3#24'#3##24'#3##24'#3##24'#3##24'#3#24'#3#24'#3#24'#3#24'#3#24'#3#24'#3#24'#3#24'#3#24'#3#24'#3##24'#3 24#1%24#1"3!24+"33#24##"3#324+"3#324+"3#324+"3#24##"3#24##"3#24##"3#24##"3#24##"3#24##"3#24##"3#24##"3#24##"3#24+"33#24+"33#24##"3#24##"3#24##"3#324+"3#324+"3#24##"3#24##"3#24##"3#24##"3#24##"3#24##"3#24##"3#24##"3#24##"3#24+"33#24##"3#24+"33#24##"3#324+"3#324+"3#324+"3#24##"3#24##"3#24##"3#24##"3#24+"33#124+"33#24##"3#24##"3#124##"3##324+"3##24##"3##24##"3##24##"3#124##"3#24##"3#24##"3#24##"3#124##"3#24##"3#24##"3#24##"3#24##"3#24##"3##324+"3 24#1"324##"!24+"33#24##"3#324+"3#324+"3#324+"3#24##"3#24##"3#24##"3#24##"3#24##"3#24##"3#24##"3#24##"3#24##"3#24+"33#24+"33#24##"3#24##"3#24##"3#324+"3#324+"3#24##"3#24##"3#24##"3#24##"3#24##"3#24##"3#24##"3#24##"3#24##"3#24+"33#24##"3#24+"33#24##"3#324+"3#324+"3#324+"3#24##"3#24##"3#24##"3#24##"3#24+"33#124+"33#24##"3#24##"3#124##"3##324+"3##324+"3##24##"3##24##"3#124##"3#24##"3#24##"3#24##"3#124##"3#24##"3#24+"33#124##"3#124+"33#124##"3##24##"3 24#1"324##1"3!24+"33#24##"3#324+"3#324+"3#324+"3#24##"3#24##"3#24##"3#24##"3#24##"3#24##"3#24##"3#24##"3#24##"3#24+"33#24+"33#24##"3#24##"3#24##"3#324+"3#324+"3#24##"3#24##"3#24##"3#24##"3#24##"3#24##"3#24##"3#24##"3#24##"3#24+"33#24##"3#24+"33#24##"3#324+"3#324+"3#324+"3#24##"3#24##"3#24##"3#24##"3#24+"33#124+"33#24##"3#124##"3#124##"3##324+"3##324+"3##324+"3##24##"3#124##"3#124##"3#124##"3#124##"3#124##"3#124##"3#124+"33#124+"33#124+"33#124##"3##324+"3 24#1"64#"64+"33#64##"3#324+"3#64##"3#64##"#64##"#64##"3#64##"3#24##"3#64##"#64##"3#24##"3#24##"3#64##"#64+"33#64+"33#64##"3#24##"3#64##"#64##"3#324+"3#24##"#24##"#64##"#24##"3#64##"3#64##"3#64##"#64##"3#64##"3#64+"33#64##"#64##"#64##"3#64##"3#64##"#64##"#64##"#64##"3#64##"3#24##"#64##"#124+"33#64##"#64##"#64##"##64##"##64##"##64##"##64##"#64##"#64##"#64##"#64##"#64##"#64##"#64##"#64##"#64##"#64##"##64##"!64#"324##1"3!64+"33#24##"3#324+"3#324+"3#324+"#64##"#24##"3#24##"3#24##"3#64##"#24##"3#24##"3#24##"3#64##"#64+"33#24+"33#24##"3#24##"3#64##"#324+"3#324+"3#24##"3#24##"#64##"#24##"3#24##"3#24##"3#64##"#24##"3#24##"3#24+"33#24##"#24+"33#24##"3#324+"3#324+"3#324+"3#64##"3#24##"3#64##"3#24##"3#64+"33#124+"33#64##"#124##"3#124##"3##324+"3##324+"3##324+"3##24##"3#124##"3#124##"3#124##"3#124##"3#124##"3#124##"3#124+"33#124+"33#124+"33#124##"3##324+"3$4#1"3#3#3#2514"142=4"42554"42554"42554"42554"42554"42554"42554"712554"712554"712554"712554"712554"712554"712=4"712554"712554"712554"712554"712554"712554"712554"752554"752554"712554"712554"712554"712554"712554"712554"712554"72554"72=4"72554"72554"72554"76554"752554"752554"752554"72554"72554"72=4"72554"72554"72554"72554"72554"72554"72554"72554"752554"752554"752554"752554"752554"72554"72554"72554"72554"72554"4254"25154"142=4"4255&"42554"42554"42554"42554"4255&"42554"712554"712554"71255&"71255&"712554"712554"712=4"71255&"712554"712554"712554"71255&"712554"712554"752554"752554"71275&"71275&"712754"712754"71275&"71275&"7127554"7275&"727554"7275&"72754"7275&"76754"752754"75275&"752754"72754"72754"72=4"72=4"72554"72554"72554"72554"72554"72554"72554"752554"752554"752554"752554"752554"72=4"72=4"72=4"72554"72554"42714"125154"142=4"42554"42554"42554"42554"42554"42554"42554"712554"712554"712554"712554"712554"712554"712=4"712554"712554"712554"712554"712554"712554"712554"752554"752554"712554"712554"712554"712554"712554"712554"712=4"72554"72=4"72554"72554"72554"76554"752554"752554"752554"72554"72554"72=4"72=4"72554"72554"72554"72554"72554"72554"72554"752554"752554"752554"752554"752554"72=4"72=4"72=4"72554"72554"42514"165154"42554"42554"42554"42554"42554"42554"42554"46554"42554"42554"42554"42554"42554"42554"42554"42554"42554"42554"42554"42554"42554"42554"752554"752554"42554"42554"42554"72554"72554"72554"72=4"72554"72554"72554"72554"72554"76554"752554"752554"752554"72554"72554"72=4"72554"72554"72554"72554"72554"72554"72554"72554"752554"752554"752554"752554"752554"72554"72554"72554"72554"72554"4254"65154"42=4"42554"42554"42554"42554"42554"42554"46554"42554"42554"42554"42554"42554"42554"42=4"42554"42554"42554"42554"42554"42554"42554"752554"752554"42554"42554"42554"72554"72554"72554"72=4"72554"72=4"72554"72554"72554"76554"752554"752554"752554"72554"72554"72=4"72=4"72554"72554"72554"72554"72554"72554"72554"752554"752554"752554"752554"752554"72=4"72=4"72=4"72554"72554"42514"165154"42=4"42554"42554"42554"42554"42554"42554"46554"42554"42554"42554"42554"42554"42554"42=4"42554"42554"42554"42554"42554"42554"42554"752554"752554"42554"42554"42554"72554"72554"72554"72=4"72554"72=4"72554"72554"72554"76554"752554"752554"752554"72554"72554"72=4"72=4"72554"72554"72554"72554"72554"72554"72554"752554"752554"752554"752554"752554"72=4"72554"72=4"72554"72554"4254"!!#'353775!#3%#37#3'#3LLhhh\                                                            Y[                                                            Y\                                                            Y[                                                            Y\                                                            [[                                                            YMbbwbb@`}j!t(mm,Leechhh                                                                                                         o{((o(,f >v  $(,04 %!%%!%31%##'137571!15'HHJnlnJHnnHJJJnnnnnnnntHHnnJHnnHJnnnnnpnnnnHJJv5 >p%#"&463327#&''5'>7>554.'&&'67%&>767'&5476367&74a &@)x   \ (K/$68'(%$I2x   \ (K/$68'(&&&% (,27 D  V(+;.## .'3%t 66C D  V)+;.## .'3%tPwDP\b6326767632.#"'67&'&'454763267&5476763353#5##73#3#3#733#736567673#&5476650"1&'&'&'#67267#"'&'&'#"'&'&54767&'#"'&5476767& MA    ""*DW/Dt$*!  BMk0&AM`z%66767676654'&676#"&'&&#"##"#"&'46767667632&'&76""&&'&'&627671#"#&&54754630'2(r '$FJD'  TR891E&  i" 9J34 X    %$  "  (!6  *(  D1  U6  -f+f   >l x~%#5##3353#3353032767665414'&'&'&'&'&504145476766303232&&'&'&#"1"001#"1"'&#33*>p>>p>=e   '     x=ertt6S    M   t?+Zfr|#"&&54763251466332###!"&5"156654&"'654&#"327357664&'"&5466327"&462#"&462M  H \Mz )@4I3A4$%33%pBo%33%   I)  ) _  )A ,$44$, A$34I4qq4I4}    8)YHjkv  $(,08CKdj+=L[l654'&'676&&'&57&"26323676765&'&'&&'&5>32#''.'&'4556&'&''7632#"'.'&667667#"&54547>12.'&545&62'&'&#'4'.'3266%733#"&&7057'7'7'5'7777777777777777777&"&462"&4632&264&''7'7/27##5'&'477'5'7'#''''7'76?77377''&'/#7'''#573''?3'7?57#'''7577#77%7''7'77677'7777'&'#/'&''567#''7737?#7377''7'7'/?''#707'?'?'77&'35''37>77/'?67'7'"&462"264#&54632#"732654'&#"&54632#"732654'&#"%&54632#"732654'&#"7'&54632#"732654'5&#"#"'&76766"767676'&#"&'&&76"76'&'&'&"#"'&76766"76766'&#""'&&7>%6676&'&  , YWt% dgf  {#U4  "  >  D %  9  ]p^j 6KH7 %  @W \P7   UUU]X]U ~RQ﨨                               &   !(                   [       $      '   響﩯⠠<*3<+4    N7C(N7C"  x<*3<+4    A-9!A-8  7','.Va,+&/VRm"4AS.#LZjPT-$&"]}luwhsdfH>eQu9Fcbp &+&-T_UH_Qj!!QZ*Q-%XgOYH&!{!"ZW3}QW|RWT  )43DQ N ^@'[ Q9  Zoq U N?NA? Zzrr     N  .@A7,+$Q   !%! NROcOQOcNROcOQPcOQORS}響K       #                       ",.w 2    !g              Px   $            v Y響⠠ +<) *= 3%'57#357'5''''77777777''''':GH44544"&H44544445454454H&"45445454ҁ0JNNQqKNNJ144444!G44444444444444G!44444444rvv#+!5!#'3#''3373?#'31?FG;< -4lw k2XC_}a acd6Ma}d6<%1%Tu )CI]jn}%4CTfu"&&4662"&&4662"32>54&&''7'7/27##5'&'477'7'7'#''''7'76?77377''&'/#7'''#573''?3'7?57#'''7577#77%7''7'77677'7777'&'#/'&''567#''7737?#7377''7'7'/?''707'?'?'77&'35''376677/'?67'7'"&&54>32"2664&&54632#"732654'&#"&54632#"&732654'&#"%&54632#"732654'&#"7&546632#"&732654'&#""#"'&76766"767676'&#"&'&&76"76'&'&'&#"'&76766"76766'&#""'.>>.'##0&55'"'05076221##'34332%33321"'&505637544332##"5#ldɫccɫhffϯf   R < 1 %' ]  T7iSv-<%&&'"#.6763267.'&>%232.'6032&'.546364'.&'.'&&&&'&>77&>'667>16'&"3274.767>&'&&'&9   >g3:  8p$   + # ;& -Z1& >.  +9 &@_    N-1 5;A  ,$#A  9 -   ';  - :/B'I%  S!53&\ "&2 ."6CF @N X?0]VB:\/F(W2d <++"% B $+$V:9(3YF& N"->v)/37;?CGKOSW463!2#!"&55353335#!2#"&546#"5!353!5353!5353!5353!5353B0ܑ00$0 `$$BPqKPqqJPq(i66yxّ00$00<<[===qPKqPPqqP<<<<<<-<<<<[========f<<>+Dn3#33!0"#"&'&54767171676323##'"&&67>76632#'3>767676##'!023211#"'#'367372#" 9'%#0:!9+2@N=KB b*r_Ck$ /1>$; 9'%#0:!9+2@N=KB b*r_Ck$ /1>$Z4"Z;B"aZ %*oi=-rZ?$Ox:%DZ5!Z;B "aZ %*oh=-rZ?@-$Ox:% >)&Rl ,7"#"'&'&&5474767##"'&&541454766323632767632367632#"'#.501327676542#"'&&547'&547632#"'%'5&547632"#"%765415##"'&'765455#"3"#"'&'454767#'&&76323%.>7632#&#"6734>1"%#&51632 %6(#8<QN+: "7.b:707B"!4B ?5 !   !  %^   )     <   %   '1  05 OR2  &/ *&+9 8&      ) !#!" C,&%  .3># *! 9+&    %                Q  !t   w  E)     u:9,=J[iw6.6.'&76.7'&&>7>.&54&.&6'&&764'&&7654'&'&766'&#"&765454#0#"1"54763254&>&762654'&76764'&67654'&76>6672676666763220#"&'&&&'&#""'4'&''655'&'&#67676'&'67654'&'36'755'&7654'&67'67756&>'&'&D[S'?4<; 1/+==$):;bo* 5A>#)]4##$ 9S                           "O      Y    #g#  ^ M_47&!L?&:! r*K54E*8k]\XW^]n88iIV,(3 3)86C                    /   N     ) $#  Z       ad9Saw6.6.'&76.7'&&>7>.&''572667677#1"75''&#"326774"5&"677'&/2765&'&''6D[S'?4<; 1/+==$):;bo* 5A>#)]4##$ 9S 0a)y/D1DOSSbMVe  S C ^ M_47&!L?&:! r*K54E*8k]\XW^]n88iIV,(3 3)86C C,7 fg pa$_`''8 $__ -, --^(  -> %   v96.6.'&76.7'&&>7>.ZT(>4<;2/+==$):i!!"&4632!677!&l)8))))~UU{WW]WizT((9)9:wvyzz<(@Y6'&'&7>632'&76327#"'&'&7676327$'&'&767032N %   _^ 6$85#U'RU`_3" U Z />,0L "KI'!  1 ,p%  4'     F?,D+5O(  >vL !!'!!#5!!55!,P?2j,bl3}}>Nav32235.'&&>7>#"#"#&##&$'>3#2234527&'&67>7&'35""4535:    $    VV  """1"&&'547276670&#"&&'5470#"&'547222"&43&'&76767"#"#"&'&##"303032#303032#32732#32703##"3##"3#"##"3#"'#"327671303 yz 1StkK~ g.g ,/q gG"  g n8/9 G,2T ( ' )  ( & ( Y+8B.o+;;+o)yL?-p&66&p/2=)p%6S2@-p&6.B7B 4:       1AEwWf%"&&'#/&547'&&''&6675&''77>7&4>7763>226'%3276654&"V '!(+)]>< #'.Q$ =>V),(!& ]15#&#Q$ =>qO1+6[[&?< "'$.')+*X*-(NW'2#'0#')+*X '!)NW,7 N0@[[@>vPc!)2".466"&5672"&&54566723236547663632#"'.#&0"&545#0".5054632#0232#"'632674&&76656.5663233>37>766"'&'&&'>763233236#"#"&&5054664&&546'&6#"43#"5543032&&'&767'&76'"#"'&76#"43%'&76uFFuuFFu  8   ,    RƀaR#   " ! ;z&!>N   L T     FuuFFuu   !    {0   #"  "$$ .P  !    b{    "-  t 25 > 6g"&&46627"#"'&547676732767677755#"#"'&&'&5476767323676?#73%32367677735#"#"'&&'&547676hԳhhԳAk,UenyB+&0pGK4rjq*pQ*oho /E/@0H{qhV,'lN0&+AyneU+kp*qjr 4KGԳhhԳhhq*G+31-(LFLt.6!7 ->x/jM/u=, 13+K=++/ 74,I*R FL), 02,G*qj/x>- 7!5/t >v<k7;k!!!3#&#"3273#533276765455454'&'&#"#%73327654454'&#"'73#632#0#"1"#"'&773327654454'&##"72327650454�#"'67676366320#"'&#5567673#67676765054'&#"01'476321232017&'�#"063212#"'&545454763212121276545454'&#"5377321276505##"'&5454547632#0#"#"'&7454'&#"327654L,    $  F2   h     6 ]          ; T     ; l ,?  ??     H         fd t         Q   J    /    I    ?x#9$462"#".54>3263226654'#"&547&#"BihhYFuYHlQ,EuYu$sqT$ qsqqiihuYvE,QlHYuFX$qsp $Tqq>'G3#3#%&54545476323232#"#"#"7654'654'&#"#"#"32732>_RRUyyURRVxw%%&<<&$$&<<ƑEDXyz[UU[z xYS6A B7//7B A6.>IEC%#"'&'&'&'67676367676767632323263232367>767632323""'&'&545676767267676767323&'&'&547676767676'&'&#"#676767632466465&#"#"'&'&'&'&547327676767654'67""1"'&'327676764%* 4  +,(M =     &(\   ! &!&6 $.)% -$* #-     )-& U !       '         &-6Is LRx&&76"%&&7664'.'.'&'4&'&676773232&74"25&&'&&676'&&&7!   d ,8(+.%  +9>6/+b`210,2*ny*vw( (5,",>86#,    4;2*=&d4)M=6"$<.";  '    1*8m:4<9 &/9#4EHLK!_6&Fqrm<% 6*!,8 g%wO%C^x%03327'&'&77'327'&547776'&'&'.>7'&776321&7676'03276677'&767630236'&'&67667.67>'>70#"'& %"$87grE" )+KJ}O/A   !/9//  OF2?7.r =?D./:;Bux@Hi!  4" ic 52"#&# '&, ;yS )mpXZR7@  ,  ` '   00  *.?" '#     !78  )5/2M'4',*%-%'%$!./ &    >v -;?CGKO#'53'7'77'7'772".46612664&&"7537'7''7'7'7Bdj6jQim6iQj@uFFuuFFu^]]\\OPPQ {(? 7`8o';' ;\?8`7FuuFFuu:]]]]I*+ooon*tD%E`z%03327'&'&77'327'&547776'&'&'.4>7'&776323&7676'03276677'&747632216'&'&67667.67>'>7"#"'&%##87gqE " **KK}O/@    ". &#,! OE3>8-r =?D//9;Bty?Hi   4"!ic 52  %$ ''+ ;zS (moYYS6=  ,  ` &   00    %(2 '#     !78  )5/2M''!! %-%(%$!./!&   >; 6Qw#7#3#32135#35#3#37223276'4453''&'77#"#"'&7777676'#3#"#"#"'&54773232327654574654762''&'32232765456'&"""#37636''&''376545454'&#"#"5#3'363023232#0#"#"'&'73232327654'654'&"73#53#"17#03#3#53#5gg? >>??1  '/!,H  k&*:0*# pQ !'0#"K   (    #!E+--$$/ "'<(''%:$jeUK 22!$$!2"7gg? >>??<[;[e[<{<"$(()))]  7,'((' ! $!%2 "#"'#%-&!q"!$+9;.! "Q02@?0-|P'',9;-' *!"##;[[ << u=PXlpx056#"&547654'&4'05601#"&547654'44#"'&&547676676.6'&547&24264&"264&"264&"24GY8 1(9F-. 1(8Q# w8'z+7#+7 &#c  [  $  * "+ a#9(   a#9( $ 6  c%0"8  C  8  XFev  4?Mfw 26=O_v5CVh[o{ $2%"''3"&'67&'"#"#"'.>56<>2654&'74.567667632676'.'&&'.&7&67"&'"3276677'67&'#>327&6'4&&'&'4."'&'67&'&&#"'74545"7&#76764.'&'76654'&'767"#"'&&>7#.#"6>67&6&'&'&5466'%&'67667"&&'4>73&&5466323&'&#"6654'676&#"463&#&'4'&'&&676&'#&'676&##67&&'654''67'67"#"'&''67346235&'&#&'6676637&'&'&'6&'&'&76676&767632&5&7&'&'&6&>233277654'2#"'&7&'45476763&'&#&#"#"&'7>"#"5&''&'45&>212#"'&&6'&'""&75'&'45456667&&'&'454667&&'&'4453&'&'&'45472&726&547667664.'&'.76#"%632&&#&&547&67667&54&'327'63#"'&'.6#476323'"#"#"'&'&&'0653326326##&'&'&667>7676.'&'&'&547667&'&7>7676#54'674676545.'&&'6323#&'&#"#565&#&'#&'#"#"#54##54##&'&'&'&'.#"2665&6767323>7676&'6763226.5&63236636&'&'6767654'454'&&454146&&'&'&'.'&'&'&#"32654&'"'4765"32323667#'"#"'&'067655&'�&#&'&'.'.'&673676545&'&'&632322"'.3662#!53!2553225!54333!"5543!2#7543!2#!"buZ     %  dB)*738 D'[(+(&(3   #?8 #2 +1X %   H8  / 6 -#  = 84 Q// 5 p =  .2 *A N 0"  A* 5;&8"0 k +  ] Y    /   9BdsSM37*"-) $ P_  . ZZ{Z           $" #% #@'& |     ;  $     '$      ) = @&%!   5;  #,   /  8Gd   1+ 3 /Q 48 8?# > Q  0QI "+   + 8& !$?4:'!    %       5 2.  _P  .*# H8&n  + 2(&(,(['D 9"MSs  $   %$  0 3    &'    ~D`WD`  J    !   $#?[ /1 l ,2!#  !<$,  ;'    "%   ,1</B    "G     z #G %4;J 7  (g    7.*( I! !18  / "    Q "/'  H[@>:r@# % $ C* #C |@C  o x  7  :+ *         K U2     G d%#        "!<1,!    '     | &(   $)2 l  '-?CR$!+#  %   #      *C $ q*I g  91! #!2, l1/:?##5 &/  G  v     >             avCL&'&&767&54626767.5463223!232547'!4&&'!&54?6654&"301266767676767654'65&"&462'&'2"&4'2"&54>&'&767677676732654'7327O 9 !# &'fQXV&( '#0C0@2&  0"po"0g$YY'&pq$+7*&;T< "+/9L1,FK=6A '$3$$32dbbc53$$3$$$3$ e 005(PI'(B,5D  '  D5"*;"@@;*  3&/ @   /)_7NG*"00"! %/$-!0J0"+-+L*. )/ 4!*<<**8?Af!%%I.'3$$3$J$$3$$3$$3$$  7( /o83%<2 (<*-+*; av>G#&'&&767&54626767&&5463223!232547'!4&'!&54?6654&"31266767676767654'65&"&462'&'2"&4&2"&4&'&767677676732654'7327O 98F'fQXV&( '#0C0$!@2& 0"po"0g$YY'/#pq"-,7*&;T< "+/:L1,FK&=7@ '$3$$32dbbc53$$3$3$$3$ 005(PI'(B,5D  '  D5"*;"@@;*  3&/ @  /( _7NG*"00", 3 /$-+!0J0"+.$9L7$. )/ 4!*<<**8?Af!%%I.'3$$3$J$$3$$3$$3$$37( /o83 %<2 (<).+*; D&,8=Lau ,?%49?DJ%'4'23'2634&5*7'5%5'67&'5667''2.'4647717&'&33447"#7&&'5'4''46731"'67656556677&1445'266767#2672663223&'"#4457221"##3""#'4454665'465467"#'67#52636465"'65021465"9"2346774&'445"#4'70"63##3'#67#7>7'665&''6635#5'2675&"'4'721#67"&575675&'&5#"'&&'&'&'&&'C&&'7"'&'3273'&#3667'53676767''3330'723464573&'7#&&56335"#726356770>223*#233.5''37>74'&'707667.'0774667'#'5355"񬳫&37'667&'230223/7674677''&'"15&'477<567676676767707&&'7677'3'6363'674767'2636673'44563472636#773''#5'7##3##'57'77'77#5#577#7'735#5#5'7#77'5#'#'77#3'#77'77'7''75#5''75''#'7#77'#7723275'75''#/7'$7675#?#'7'7'5''7%7'57%7%7'T!A 3  l/(9PFTT';F=; P_  pdCP  R/4  &/  '" ED 9     H* 1 2   ##  P  HAc %     hG%, $ -  $$1/,8  $5 FJ  G!    G ?B)A      T). %    P /  6DEK%%# @  OݘEB 0nIK {) D A'(  cE <;      7+!9C  ?@8@56  19RQ 6   3CYi  > )  *,#$mQX N  Z:]V?7]<:&"dt"&Zn:E 12m*,za), xB495           2    @0'6. )   # M ) #" ! C:4 %  4E 0 !  6<8C9R;04           b  o18S"   '*)(9 ).1{#;5- > ]   24 #   (# E  $-$ :9 6' . 68%  '!e $.1 $+(%*0## ( .AF -+!$"  &!5/(R3P,RS%~# $( ' $      އB{+%&tA>w>M]hwx 4@DIMQUYa[d<GHLRX\ijkostu#3&&'#67&'5667666767&'>7&&'&''>7&'67&&'&&'&'&'4667&5474>327&'&'06667632367'.'&'7&547'670'676767&'67'&'66.'.'67777&'67767'&6'&%&'&'"'>'67.'676767&''>7&&'.'&&65'4&"'4547&'#4566767>3676767767.'6?67&'6'4.5&'&#'667>67&'767&'&#"67&'&>7&567767.'1.'77&&'&'&&665.'767767&'&'&''"#"6'&66'6'6'66#"5470167&'&547&'67&&''>7'7676&'&547&'67&&'&'6677016'6&776%&'&'"'>'67.'676767&''>7&&'.'&&65'4&"'4547&'#4566767>3676767767.'6?6767&'%.'667&&'66767>5&'4657.670&1&&'77&&'67"&'&&'67767767.'67&'&'667"#6727&'266&'&'67307676766767267&'4667766776676632&54676>7767.'&&'&'&'677770'6776770'676767&'67'&'66.'.&6'&'76#"54706&'&6323676667'&&'&'7&547U+sp-vwB/02)=z{64 RJ(gg(* /  ii'&'OO$5TM> %  - (6  J  ##?   4 # '+H     3                &  z+  z            % +' 1  HC'-<=1.    0 %+$   {H( ^ F#    g    '              a    *q  >     [        #d  &,  b     r 4  +  z            % +' 1  HC'-<=1-H( !(''=GTii  / *.1QT6       , % 1=;- !%     1  '+ %              x(6          #F @       . .   :      '  3           9   r(* #  62dc?w!}T`a!{!3& FFXBC:x      N@ #   *    )    *        / E j   k|"XT!M   &   U   hww85 E("rO   9 8*#  ](   y     F         [ (  n       <"    &=   "    $|"XT!M   &   U   hww85 E("[*#  P"}&,4FF &2!{2:   !   *#!)I ewwV   U    '      Kv #   J      Q (        O    / F       !     t (   ?vBV~ 1En,Am.'4546762'6767654&&'&"&'&'4546762.'&54767632&'&'&54767632&'&'&&#"&&'&&54767632&'&'&&54767632&'&'&#""#"&'&4766323"&'&4766323&'&'"#"3276767"#"'&'&54>7&'&'&5467676732767677#"'&'&547667"'&'&54767673276767#07"'.5456670#"'&&545676727>545&'&'7#0#".'"#"&'&'&'732767654'&'&'7#"'&&'&'&'&'732767654'&'&'7667232#"#&&'"#&'&'72326764'&&#"'6767232'>7632'67676654'&'&#"'6767632'>76632'6767654'&'&#"'67676632"&462"&462"264%   "  ( 2* & ' V**$    # +6| .71;*(!#- /"#')A~$$~&  &,*=>)+  +)>>*,_$||"(*<>)'". . "& X)** + #'    $ % /  "    X &"' y$     # + **|_#. /"#')A;*("~$~+*=>)*  *)>=++'&7.  }=*'"/ .#"(*<"& +2 + #'    $) *lllqqqxgggI=$$=I>)+  +)>>*,&  ',*=I7.   |3. ""(*<=*'"/W ' "& +2X   $** + #'  g%(  "  *X &"' *%    # + * VG<  `!#. /"#')A;*(5,, $~ ',*=>)+  +)>=*,& 0_$|(*<=*'  "/ .#!V ' "& X * + #'    $)8 &)  "  (2* &   '   # + **%  ~G< .7 /"#')A;*("# .Illlqqqgggz-7K%30326654.''&&54663232&�#"#"#"#"&'%32653##"&'&&'#"/&547'&&''&46675&''77>37&4667763>26''327>54&"K8"! @@&H.-LT%%JDcN;a k)F12Q# & !  #&  !  $4%$ I=(@%/'5 J?FO<2)&l6J!6,v  #   #   #$G   %% >v "!!3'77!5!5!5!5!5!l#e\$;E-%p^:##V""h##g""h##>wJ!%732653#"#"&'%73012654&''&&463232&"#"#"#"&'>lzC  RB9&?#C +"12B6#:@94#=(-Jj) >>)" ' 8^>$) 81$4.&>O0>HRVh|,.4>2"2664&5!%"#"'767#""'66.&547'654'73'27653#"&'7265##"&''324663232&#""#"#"#"&'732654.''.2326654&''&&5463232327'&&#"#"&&'''3012654.''&&463232&&#"1"#"#"#"&'%32653#"#"&'MuFFuuFEuڸllڸll>:m 8b  eߡ: 9oW@"=T"T="@$H N,"?'B@ GNn ZK.<'$<<*"@9RC/P?3( $,|/I,?C  WF*AQ=9. [Y 2#89L>(BI !B;VF4U ^$>*+HFuuFFuulڸllڸ$$0# #0\##³ںI$ 9J")"'TNGI6)2a!4#&% =5:A.'% !4 @+;D   8$,K99D  5.7  @kG)"/ @8>E4,%!A/A0&>v6J!%3032654&''&&463632&�#"#"#"#"&''32653#"#"&'>l!12B7#:A95$=(-J R7%&?l  8^=$) 80%4.& )9)"^Q23276765454767676763233#&#"3233#"#"'&'&'&545454'&'&#""1!"#"#"##5323276565&54767&'&'&547654'&#"##53232302366545454&'&&&4672&6654.".5466^  "- , -"  j"**#5=$ jj!MG0-8E"#*#"P:*2& jDvE 6, 5NO+,FB77& *6: S B 11 ---.01 B S :6*&87 BF,+@('ATc9U+ՙ6X2%UE'+ $0'AX;6V0 jFwG,X:=U>?RH676767630212#"'&'#0130212767650414'&'&'&#"5"735#&'&'&&5055414'&'&'&#"#523276545541467676704747676763032##0#"'&'&505547676763032##0#"'&'&541'6767676541536767676=414767&'&544154'&'&&'7013203&#"1'@   P -       F           (A(       F    .E> "0 6 0" * 0 1  |     835 0 0 * 0 7 1 *>LE1%#"#"'&55654'&#"#533276504154763233#"#"323!32327654'5&54767&'&5455454'&#"1#532323273#&#"#"'#'#"&545&'&&#"'&&546325>'5&54676323#267654'654'&'&&&4767327454'632&#"&47672327654'632&#"&4767327654'632&#"E 1**3!^"!  1**2 ($4 +% 4$ %3  _   #/          /IN /$NJ/Q! $QQ! $Q/IN / NJ +'4$  *%3   * 4$* &8$ !    (5  5 !  5  >v#'+/37;GK".4>25'%757'75775775757#57'5755'5''5'FuuFFuufffffgggfffffffffgfffffgffggfffuFFuuFFu9f3f333f333333333333ff3ff3ff3ff3fff3f33f3f3f3f3f3ff3f>v,_%6654'654&&'&".5>72&>54.2".54>:G!:&Ze&!( :zrY6vv+efT54JRb/&: G:A@;-+786( :zrY6vv 88;-*8?CVmB ,P<EqJ9%JfQvv@W{E3tlM ,B2Lau632."#"#"'673276654'&&'&5466'327'>765654%6#"'&654'224d+27- #$q% 4<6/ a$%-<    ,DX!* %7> 72e  )-$ 7'5";-*!/&5*!n;.) 45IHor V[lQnpkp#! O>?o!%!7"&''73326654&&''&&5476323232'&##"#"#"'3272654&''&&545663237&#"#"#"'"'73212654&''&&54632123232�#"#"#"#"&''32126654&''&&54630323232�#"#"#"#"&''3266553#"'"#"&'>RI%?B'  ','$ A  /,>5g5.6&+%0 .$%+ "&'2$!2  ,*<0#; "  ''3#. 2  -)0$:   A3$1 ,P j#& '"  .*')  0),5G+&$( )- "(  $  ,%#2     ,&*/$  ,%#2    ,&)$ %3 zh-7K%30326654.''&&54663232&�#"#"#"#"&'%32653##"&'K8"! @@&H.-LT%%JD/A&;a k)F12Q$ I=(@%/'5 I@#9%<2)&l6J!6,? O*KOSg#+7#"#"#"'&'476323232676545733676753#5#"'&'&&5055#77#732767#5#"1""1"'&'&&541467676766775454'&#"#"#"0#0"1"'&54147676763032321$462""&462&"264462""&462'"2654.6462""&462'"32664&&462""&462&"264  Li  LL NKKLL    LL       '')** $$''(**3$ '')**2  ''(**!$$q        ]    9          t''**A$$%''**A$  *''**@$  ''**A$$xG&5E"&5467232267"&'">32&&"&46320#"&463214,h1&޳%0hh0`Jo&1'6'' v  D8ee8DD9Cg:~f9D''6'  x   < %371'#5#35MjTbdVEceԂhCVck\t]CZLXX1sSKS]gt#'+/37;?CHMOQSUY[_ceiosvz~!3!#'!&&54673!#5!&&'&'0&#&/4'4'&&54&15&'&''4&&5&&'&'&/##147674767645766105677&'5531771'71&&'&'&#&/45&5.'45&'4&5054'4'7&&'&'&'&/45&'4&'445'7>?.'&''&''711"'6?1&&'4''&&5'7&/7&/4&'7&&'&'&/"67616765>5674>?166?454'56746657&&'&'&/>7656747667657&&/36766577677'6676?#4&54767545477&'&"&'&'&5476467636#"'&'.'&'&5476467636#"&462"7".'&'.'&5476767>763232654&&#"&462"&462"&462"7#0#&'&'.'&547676767632264&"76#"'&>&#"676707632'&#"#76#'27654''&54"#"77677&'&''4'653066767'27#"'&72367672''7'7'7'5&&546&#"3273267&&#"2/476"6332&"2#666667&2&"&&'#13&6&160#26#2#406664446!&2662267"&732&&#".546&#"3273267&&#"Ė44  4Z=       #8;+@+    2+     ':    &%         n        , ' (       % ' \>X>>X           n-@2-@?:>X>        @.3@--@CV<223"'&&'&66320#"'&&'456626545&&#"q&(pM !)pNoMm IhhI8."(XB +8O87&  Nn1*$LoNoMohIJg J+AaJ4"(77(&5vN %' f >✼wd>v%+17;64>2"&&6'1#1#"''7''&'&4775'>FuuFFuuX [  M^^ff%^ӲuFFuuFFud  [ L'Af(f>t )=5!%!#5#33"#:3#&&'"#05353>67#"#.'&'67>;~~I;a~*32$# *0oo{i`a2231j41>v!!!#>jJlJ ;s/27:JZjz&4762"'&47762"'&47762"' !727764''&"727764''&"27764''&"727764''&"CA***+GfJ"  # #  " #  " "  # #  " "  # "  " "  " [B*"**gkkJ" "  " " q" "  # # # #  # # q" "  " " "z #,5?HSanz !/;JX'41&5&676>7'>7'>7'>7'>7"7662&&'66.'6.'6.'6.7654'&76676'&&"76.7'&67>2&'&7676'&763676'&#"'&>6'&7'&6>6767676326767266&&6'&&>7'&7767'66#"#.2327.BP;IO &&7V-C(.8]!Ad'6FW^!c'M[hC]Xn!Zow)8.Dj9 Q*68R (0%;%-+/)'I3O! !&.-6)[BT-)83B2('(, U  / ' )1 7-- % !  >  D- #,1F * $  !."*     & $&9 .:''+  BE+GP B($$ $,!          ! O &   V   ;'  Y$-: (&"#" ")/.($  $ ] 50> -# #  *6#9'A    X>7 K&146 %_/ Td$Dr/V!SB:"%;GX C75%%   "#=   > '153353353533533532#!"&'663!!66 t;t;t.t;t;tz"00":"00"uuuuuuuuuuuu0""00""0%>w  -@FL\cpw -4GKW`frv|     ! 1 `   # + 0 > H L P d q   ) - 2 I O U Y p % . 5 < K P W b i y    ) - 3 < @ R  #)04:@EIT`k  )-17=FKOZ^dnrv  $,04BGL_cgr!&159=OY]fjosy $-Eioy} '+/3HQX^!%)-17;?CMSX_y,26@DJN\`fqw{3:@DOSY`dhq  &*17=GQhrx '-3<ELT^flu}KQW\`fjptygntz :EKR %+15;?EU]dhlrv &*/49AE]sw} *:?HY_v}   / 5 ; D M U Z ^ d j p z !""" """"-"3"7";"A"E"M"S"]"a"z"~""""""#&#+#/#3#7#;#?#C#V#Z#a#v#z#~#####################$$$$$*$2$8$C$I$Q$j$q$x$|$$$$$$%% %%(%1%5%<%B%P%T%g%s%z%~%%%%%%%%& &&&&$&+&6&@&M&_&c&i&q&u&&&&&&&&&''''"'>'N'T'X'\'b'j'y'''''''''((( ((((%(,(4(:(Q(W(a(f(p(t(~((((((((((((()) ))!)%)));)?)E)I)M)U)Y)])e)q)w)))))))))))))))))))*****$*)*1*5*9*=*H*M*S*Z*************+++++2+u+z++++++++++++++,,$,(,1,5,9,C,N,a,e,l,p,t,x,,,,,,,,----*-2-?-Y-_---------... ....+.;.Q.Y.`.f.m...............///#/'/0/D/K/O/\/h////////////0 0000#0*010;0B0G0n0r0{0000000000000000011 11110171H1O1S1\1d1k1q1u111111111122 2#222;2F2Y2]2c2i2z222222222222222222333333&3*3_3i3p3u3{333333333333344 4Y4a4h4m4t4z44444444444444444455 555#5)50575K5O5Y5_5j5n5r5v5555566 6666$6-636<6L6T6X6\6`6o6s66666666667777'7+7C7G7S7W7^7b7m7s777777788888%818?8G888888888888899 9999%90969>9B9J9V9Z9^9b9f9999999999: :::":&:-:3:J:N:U:[:t:x:|:::::::::::;;;!;%;3;7;;;?;C;W;_;;;;;;;;;;;;;;;;<<"<1<9>>>>!>(>,>2>;>?>C>M>Q>_>e>i>v>>>>>>>>>>>>??????"?*?0?A?K?f?j?n?r?{?????????????@@ @@@"@(@0@9@?@C@G@K@T"6&"'&'&'&'&#""1"'&'&541454765654"54'&"'&547654'&'47676'&'&'&'&'&#"'&54'&'&'&#"1"321'&'"766''"03"'&'&1&5&'&&'&'&'&'&"#&'&5474547654'&5445&#5&'&'&'&'4546725&'&'&'&'4547654##'"#"5436767"''01'4'&"#"'&"4"'&'&'&#"'&'&'&'&'&'&54767676746547676336767276'&'&'&''#&'&'47667672'&'&'&'"32366243276323766763212476364763676765&'&'&''45476767654'&'&&'&#"'&765454'&'&#"567674#"&747676767676'&#"##"#&5676727632765&'&'&'&76326365&'&'&'"567632303276'&'&'"465&'&'&'&3&767420767656766'&'47654"427054'&'&'.#&'&'&'&'"'&&"'&'&'454767655676#&'&'&"&#""7654'&"'&5477'"#"#"'&#"'67676'"#"#54767&3236767276767676763234276&763234##"#&36"&#&'&76'&623676763276676676'&'&#"76767614'&432767676367676&54'0454"32127054'&76762"4#"01'&'4'&'&#"367632120"''"7676'&545454'&541414742776376367672#"1#"5432'&43223277'&6676767623#"5&&32764'&'476762#"225&'&3#&'&"76'41476'&'&'"7627414'&76276323767232"76#276'53&#"'&"332'&'4'&'&'&'&'&'&'&#"327622223232323676'&6367676767676#4"'&6#"'"133677#"'&7673&'&'&'&'&&&7674#"4#"#"1"'7367&'&'&'676720&''"#"5&&&'&'&#&'&#"'&#&'&545676326'�#'&'"&#"&54"4'&676"5054'&#"#&#&24"7454'&6'&76&25&"&2'0"167674#"&24"67674#"7654'&3177&#"&2'&"'276541"225&#"27654##"224"2''24#"254'&"6'6&327414'"#""7654'&34"424545&"6%656"30'676"7674&#'654'&'.1036265&"7"70216764'&'&762''&632325&'65&'&"&#"#63$24"65654'65622'""#!25&'&&"422&"%25&'6'&#""2'4"24"3%64#32%676&#"225&'70545&##"%24'&5456'""#256'&5054#"27327654'&25""#24"445254'&%274545&0224"%674#"0%2'&'62'46265&#"#&'&'"3223'&"17&2321276'672665463676&#"'&#"'&'&7'"56&&30762&"44#"37"762#2547627674"224"25&5&#54&#224"25&'&#"32%276"#36765&324&'"762'&64#"7254'&%445276740%654&24"24"674#"'&#"323307654'&527674#"#"7674#"32%0&5&'&6&'765&76&#"324&"7654'"#"77$2&024"7&'&'"%676&#"&%65054"325&767'1'"7655&654#"%676'&#"024"64#2571654505403324"37654'24"47654'�#5676""25474#&#"16226#"765054"25&'%6&#"244'4'"%6545&%676"&2'&546'&'10167677#"&56'"76767476"76276745576&#"&71'#"4#6"743237"4214&%650547674&7'72'4"324#137&#"32'4#"#5'33254"3676"76"7225&'24"254'&24'&%650&5476545�76'&43265&32543672'&'&"#&224"2'4"%6'&322'&127654'""#25'454'&76#'"224"254'2'4'"224"67654"74&276322325632'57654'&7654"6"7'&#"6"4264"676'&"704524""2'4"4454'224"6765454'2'&1"'3224"24"27654'&$24"674'&&322'451254'&033274"%64#24"225&'04"0%24"324&3 24"24"332&324'24"%66'&%6545&#"%254&30327674#"#"'41476'&#"#56765454#230322224"24"276&224"$24'4#"254'276505&'&0'&2254'&2'&#"3626'"254'!6'24"24"24"64'!4545&1224"24"765&'224" 24"254'25&'6&1224224"6'&'&$&24"36'"'524" 24"676765&%67476&"#"'#27&765414632766&"74224"27641"#"&24"24"3224"25&'24'&'"24"24"376541432&#"64"224"254'!276416"4225&'&"24"24"76545454'&%64#64#632'&#"#'"1 24" 24"27674#"65414#"2:6"2&"24"27441"24"24"254'2&6545&'224"6''&24"276541&'&!7&#"1365&24"6&7654'&5&541414'&367"'&547'&'&#"#26"4457676&#""224"24"24"27674'&%454'&$24"%676224"44526"24"2'4"767454'24"04"324"361&"%24"!2&'&'"6767676'&&''767'&'"%24"34454452'&#"304"345454'"#""2324' 254'24"24"6&5&'"32'&76"7�#"3676276'&7#'&3'&&'"1"323"'"427"7432124"27674&5"420024"24"23276'"&&'&'&'4#'&505414'&63'"42'"420&"42#&'500&"427"42454524"254'&3$24"24'&24"24"3364'27654454'&$24"24"24" 65054'&"#&6'&505664'364"2&767374#"'&#&'&3#"'45476#""6762'&545#24"24"%27674'13676724#&'&'&"'&50561"732'5&"422'&"2764'&"#'&#274545&"%6'"1"224"24" 24"24"24"2'0"124"$24"24"2&'&#"2%7"#"164'26#"3267674#"#2672''%445"21276&324"304"1764'7&5416&#"3&"4276545&"0725&'24"2476#"324"24"4452'4"%7"#"#2327674"#"'&'&36767"#"#"#"''"'&#"6#"763:'4"24"%6325&&24"2'""#24"04'&'.#&24"654'3654552$254'24"24"654#"2'&#"324"3225&'!27654#"3744527614""242'&"2'&"24"%6554#364'2'&#4'4'"2'4"24"&'&"24"!24"076656'&332577414'&54'�#"'&#"3254"3654'24"654436224"254'25""#24"24"25354276767422'&#"24"24"24"254"!7""#324"$26"24"324"3276#325'026#"22'4"04"1224"%44524"6545&"026541&7505&#12'&"%24"32'&324"3224"25&"7652'""#25'''3656"767&'&''630''&54724"3%254'&224"24"2'&"25&'7672&'&#"##6454'454767676727676'&7627'2#"7654'&&#"&56#56#"'&7676'076"76764676#"76#&#0'76'&"#"'&'&76327477'&5'"6765"77'&576323276#"5""'37""'5547677676323367676#"'"76767276773''76"425567672#4776"4250474302"42'0676707"567'476770"42""42"47630'77"476307"42#'"42#'"42#5"42#''"42##3"7672'045632'76722"42'"7673"7672'76'"76767"42#'4777"4275676&"422"42'"742&"422"42""42"56#"7632"'74#0"42'"760&467232"7627"7632''232676767674545&'454'&547454'&54'&"56##"&#&77"42'"427'7&"42#04212"42'477"42'77"427"427'7224"445445!67654#"1&224"24"3676"'&224"2'4"24"7654'&56524"67676&23277632767423745667''6&#674'&"##"'&"#"'&#"3232767674'416767&'&'&36"337&162"6"762742#'60#''66"742'56'5'"5223'6'"'&234"422"7626"7426"4224"7674%54'&'#767&#"2'0"127414"0'654#"2224"372764#"3724"3724"3764""5454""'4&#033543267454'&"7642765"677'&''##"#1"5&2'&413232724"2'4456'&'&5&5'3'&'72'&#"676&'"2254#"24"27441"224"24"24"%7&#"654'4''24"676'&"4##224"24"24"!2541&"03764'25&'&"324"3225&"24"654'24"24#"2274#&54&#32274&224'&24"24"24"1224"!27674""#"#1%6767654##"'&624"07445!24"325&'0122764#"24"2'4"24"24"276#&#&4545&54767654#74#7456'"'&&'#"#"7'&540254'24"44164"74412''124"6767"#"'&67'&'&54'&72327#"&24"24'24"324"324"324"324"$64656#322&"%24"724"$24"24"2'&'"24"44524"24"24"124"1332'4'"06"42654'&27654"""3227454'&'"7664#274&"32'&7654'&03324"7676''&'&7'7632'"52234"4"!24"324"'654&2741625'327677654"6&25&'&2'4"225&'44567#433'&'&7'32324""24"2'&"7476745624"24"65414'&'&2'773304"17045326#"3326#"3324"303'44524"650& 2'4"$2'4"27441"7640767654'&6"4224"6'&547#254'24"36766327676767676'4'"'&#4"'&"5436'&505414'&541'#"4#"'&367674&'&7#&'&54"76&5464##'&'&2#"'&54"'&'&'&'&'&'&#&7623273277607632'&'&276327276763173607654'&5637614'&5417&""65&'&54762773:#0#"3232&76"7021&"426"42&"42"'405&"42'&'&5476'04"3"42"42'"'72"42"'4545'"46#3045632&"426545&'&'&54766$24"27441"225&'24"05'76'0#"776#05&'&45054'"&'&'&'&'&'&''54#'4145&'&'&'&'&'"347676720764#"760552'50''7&"42&"42""42&"42&"42''76764'&7#&224"445"2656547655024"24"2'0"1!24"3324"3%44527624"64$24"24""24"674#2026'&"24"61&"$24"24"254'24"76&726"7021045!04"1224"24"00763&"276767254'"#"24"002'4"74545&"%24"325&'&36541476'&547##"5324'&!61&"224"%3276'&'"0%676&23276&27674#"#36#0#"'&50654'&"#327$24"6767676'&#&#71"24"24"3224'&#" 24"24"676767414326'&76747'676726"422745" 25&'%6'&545524"6"76'&2'6'&'"224"1$24"254#03365414'&327614&##3676'&505#7'&"062767463542'�#%4545414/#37''704"1725&'&50654'4#"3%24"%4545&224"254"3%674%24"1376543254'""#0225&'"'&545524"2'4"676#$24"254'&54414'&0133212541&"0;67674#"##"1'72"#"'#'24&322'&"6541%27054'&7654'&#%64'%254#"7#332763235214#765624"2'&'&'4'&#&21432'"7454'&432&"&3&"424"42'"422'4"327454'&3325&'&624"24" 24"24"7637&#327441"36767&'7654'&57654'&'&&"2327#""""#&232321''66'&76654"24"2'4"656##"'24"764'24"325&'$54424"32561764'&"24"32746#"264"!6556"422764#"'276#&'&24"326"332765"326&'24"%24"24"02746#"325476&#"5&5476""'274545&'&24"767'06#"1!254&327624'&323224"24"676545&#"'&6"422'""#24" 24"%>50224"24"2''06'&3224"32764'&#2'4"24"30''0$24"2'4"24"6505&#"764#24"27454'&24"3657%67>'&77&#"24"3724"3724"362'&"24"3$2'4"454524"454'&'&#"17445%674#224"24"24"454"0744524"%2'&"305&'&'&'&'&#"#0#"132676232'624"65654'&&&26#"!7&#"#324"332'&7654'&24'&224"%654'2'&"64'6&%67676703274#"32'4"32'4"32'&'"2'&6'&'&"241'6767&'"54&"76#"30#"'&245676727"#"#"#"#&"27'624"022'&"7654'&''&"7'6&"4204"132'&#"37&#"32&3327441"3250'4#"327641"'70#0#2'&'"24"376765&'&'&#"36732%2&3224"654'&224"24"6&2'4#0650'4&%677654#"5&24"6&:4""24"24"3414'&24" 24"427454'&2&"24"27'5''&763232'&'"'&767627655'#"&767'"'121#"'&223676763032$2'4"2765414'1"7624"!2765"703276'&"7"'5#34545&"76545445&'&#"32362'&"6767276545765&#""%25&##"727441"706#"3$24"24"44576323276'""#6'&'&&'&#"'&3236023276722176074''7&"702324"6'&''241"7654524"34545""1720'&'&'&3%663233'&77677#765""337&#"1254"2565"765454'&'&'6&#"376545&&#"7654'&&24""2'&"24"'&'"227054'44524"!24'&375""#336'&77654'4"724"2'&"%67677"#"'#'&##"##&#"3237#&"224"24"21&'&36505&#&2'&6326'&"24"2674#"3'27674#"%2763676'"76541&3&5&#"###"#&033363&"32&22'4"24"24"25&'&327654'&"54&#"'&23262367632737674"$2'4"24"24"25&'&322'&"&24"66#324"0324"0324"07656230%24"076&3654"4#"'6'"176'&332331332724"261&&22'&"&2'4"656#"'&3024"256#"%7&#"137&#"#224"24"24"256"#%6'72126#"222'4"4"7&#"76541&16"42676&6224"24"322&'2'&"76''0'&'0227&5%676&#""572'4"224"2745"!25&''2'&#"$2'4"24"2'&'&'4"4#'&547654'367'"7654"'&'45456&33436333&"42'"422'05472541&%2'4"77&#"2'4"3676&!254&3%24"3276545'654''"#76'&&"3272'&&366#"764'&547#7624"254'2'4"%676'&###"##3276&70545&#"324"224"6&76764#"6"4224"0025&'674#"'&3024"44527054"1%64#676"$24"25&'&24"6767322'&4#"13276#0#"24"%7&"24"2'&"224"25472'&'&5476&132"056"623232'"6563004"0764#2'4"$2'4"7""#3676'776674'0#"#0&#"6"4225&'24"345054'&327441"225&'254'&#"%63&"3361&"224"7454'&'"664'27454'&5456"'&'&'&'476'&'&"61##"32325&'454763%256#"725&"7&"24"125&'76545&#'"6"426"4224&24"70452'4'&054'&!24"'24"364'7&#"676&%70#"#24"324"324"325&'&%67670""677#"65414&26#"7&#"#22'""#2'4#"%2'4'"76545&54#&24"25641"25&'2'&'&'&24"24"24"6767&#&#"224"7"#"1$24"3254&66'&''0325&''&'&'&'&56#'077#&'&#"""32327676374'3'&'&33'&'&'&''02554'&24"24"7&'&324"365&'&%24"674#'367676#"#32'&'"$24"24"24"#274545&#"#"224"&'&'&332'67627'&#"'&&3677654&1476&"422"42""426"4265454'0324"0676#"'&"'%66'&'&'&'&3256#"32'&54#"766'&#"24'&'#424"04'&325&'&'214"622'4'"#"#22&"2760"24"27414'&2224'&#4'&'&'&'&'&'&7'"#6"'&#"3276323632323224"374412'0"1274545&3725&#&'&54#"#&72'4"%26276545&#"#"'&326#"74'&"24"324"%2&254'&"727#54'&2746#"7676545454763267414'&&'""#3236&2&367672&76'&764'%2'&'&65&'&'&'&&0374#"224" 24"7414'&227454'&24"24"2'4"%656'&'&'&'&'&624"44524"025&'%26&3274'"#"2'0"1%676#224"6'&#454'&624"24" 24"24"64#4&7#'&740"#"#&5456"'&6'&'&&5416&#&'&'&'&'&'&#"'&'&36'&#"#654454'&24"'2&727&#"32741224"!7&#"32'&'&#27674#"372'&'"224"24"!7&#"24'&0&&'&'&'&'&3&"4225&'&24"654'&6'&#""7&"4224"67'$2'4"05&'&'"454'&'&''&'&'"13232674'&"632326"25&'&3'&2%276#"32327'"#32!24"3324"336762626"&33222'&"24"6767674$24"24"24"24"265'"&36&"'&'&'&'&'45&'&'&'02254'24"2'&"327676&#""'&'4324#"72764#"224" 24"2'4"274#25&'&5&&#&''1224"26"24"#254'&#"%7&'&#2'&35&'&'&'&'&'&&'&&76#'61'&'&3'&''676727"'46'"423&'&5"42'"42254'24"3&24"674"76545& 24"24"25&''24"25&'&32376&76&$24"24"6&$24'&276&0367674#$24"277%74#"'7&"24"22'&"324"304'&#"%67&'&'&'276505&'"2337&#"2&""%25&"2&324"%3'""#25&'6545524"337&#"1224"032547&53$254'24"22'4"675'&%24"30&#"2&#"%2&''24"'2'&'&'423'&'&'&5&5476"#"'&#"13#&!24'&''&25&'25&'24"25&'&24"654'2745"224"24"26232'&6"427654'0#"7624"$24"274&"#"''67'&32$24"25&'%67#"'&#"#32 24"676432&&6'&#""24"24"654'7&#"$24"054'&'&'&725&'&'4&'&423&'&'7265&#&24"2'4"6&$24"2'&#"04"1!3276765'"%2&5&'&26676767274545&'&""24"224"24"26#"30'27641"6262424"24"2'&'"!4545&&"76505&#764656545465414'&#"'&6&6&24"!25&'&'04'&'&2'&'&'&2'4"$2'&#"'2'&'"767650#&345452'""#3%614"6&2'0"1$24"&24""24"'24'&'"                              "        $#                          )                      B#4 7+Xtk  7"}>d 6/3t- 5p%  um}y[O _Y F %?A=    &m6 |) $ )   r5%*,IvR+  _m B jR6l& BkmFK PF Ipq  h 4; )|$=D 3H< l,_5!t    B` 0I3'N d  ZP t  >}(H!  (G6U UF)Q  f8%30L v^LA4sG/u-y6 41 ;)"  + &06qKF9 yQ*   +< X                   )1>6(" 4"4#   U \.  ++G    " ?SM_q#&         "");8d$9~l Z0Cr- @Qf#l  n k   O B !*W 7P 3b   *!  :4 :1[Y!k   'S*E g t Iy &9ZN$e14-le'         >3 <j.0X=  CU     1d f }Pi g,  B K i=.P  -&    U4   og l j: *? Y~;47Ip0 W7C^ _I&L )&g'dNtg  VKl Dm  Jp  () H 'O 5XBr  E-<     E  *}*$9 T(=UOz    a)  7+eC%     N54S!,Mp4T  A0 1g =O0  Xyf>."j gi~ZL5  , J&;oV1@ U> ? i=Z;$9C V/" y\93 &x3   |8M T de X EP BU-h( ` D  `Rnre  /V           (xt h(e  -   'd;     9Vb#Vf~  e]a^#1[2+dTry  =~9  9o   4Z7 ' <^E 0e :q     qc\;"   W <6e]       : L;  pp       VT yDU \Q w   +W~eZmK$     PJ1 I p   vF%<     0HN P  a6 - B~  2yr                                          ""                                                              +/                                                  %           . _     O         :>                   Y          S                  @                           "                        &                   ,2        #"                      B                      ;YJ*f#%#!"&55463!2#"#"'47672327654''&54547632&'"1"#"#"'47672327654''&54547632&'"1"#3232767#"1#"'&'&'&544147676767350414'&#"1"#"66'44547677'&'&74454'&'53##7<5&'&754'&'*#3276'4454760##56765455454767&'&5455454'&'53%&7673##"'&7454'&'567657334&5&'&74454'&'676'4454767:35#3##"'&54=4'&'5676545545476767#"#"'&54553323| ,,u+& .& .a`    '  e0         ~              $               ,       ,,,r!  &    !  &            '  #  '    0   9"% /  $ ' ,  @  4 /   7  "  "   %  (  o&9  @  %  #  ' / 4  @  7     Uv&632"#"#&&7>3>766767654'.76766'4&'&6'&&'&65&'&'&'&'&&'.'&&'.'"#"654'&"66266766667 "7  :4U   :vqZ? #C1+    "  n=O"  ' "&*   & +    &1C E\ieS (   *    >=/[  !"   '&+%    F //)"   Qv 4j 048<Y_dhmq$\qu 048<[afjos4Takv7''&54776372'&'73277654''&'"'&54776372737'''&'732776545'&'"77'&''&54776372'776''&'"&''&5477637".72327767654''&'"1&''&4?6772.73277654''&#"737'''7''777377'''773777777'7'7''&677567731'&#3277654''7".1&''73#7''''''''677232''&541777454''&#"1'677632'''''7'&77177454''&#"#7'&'7477632''57454''&#"#&'7477232'.7327677454''&#"#&'06654?2320'.77>1454''&#"#'''''77''7''#'''7'7'7#''773'77'777667'&'7165654''77'.1&'77''7'7''76&&"#654'&&327&'&676&654'&&327&'&676&#"654'&&327&'&677&&'&&7632'&21#"43076"454'&&6@0U  %R  Z :5Y  &      0 U  Z :b s &X %L   Z:&Z %    [ :%I %*(    [ =       )        !'ؕ | 1         *+ \0*&] \   !  b7,Z#$# " # D< 3 :* y o  >>@G R$ o  <  . 3 : 9 ;H S 3  o  > H T+  p >$H T %j p =  I Y   - - 9    /":#:* :224;2M334;3]       7 8$ : ,-.  A K K  #=   ?# &  0+) X 4 +y(,4-), %[!! !)$/k%+5+}()  \## :" /e$)" "!Y*=>$&!0-  "!]#& \#M; [ c LIGKP E k LG0    & .\2KP E HLkLICL Q  FNMlMGC5Q FIKX LHC / Q  J-    !  , ^-''@'#' ''    $  ._ ]170a1171b1b $ ! P  ) )  YS MN b P  'HB&"$""\GFFG*9?zv  ` R !3M*;? +   ` R !4    (  u5I&;>  R aS "1n ; > 8S aR"1&!!< ?   8() `R!1   <  ?  1 " # x0.+/*-+ +*   &    z xjH6;HH6;H$    &  # j-* RcZWlA@ 4  ; )!1)2 $ $$L KLL>" ;4C  +#'\'#6:!!!88F  .&*\*!%38!!66D1',[-"'15$k/-) (0q #  **91', Fv '*QR!!''!!#%2##"''&##"##"5755433'7#&#"#"'415432654.54320237"##52>54&54677.54654&#732327654'&547635#"'&547654'&'233"##52>54&54677.54654##'&3#"&546548654&54>##2335&'&5547654'&''72>54&543237"#"3#"&54654񿯶&54>#U{j$    /  / , 0   (     ; , 0 " h 0,5    )# 0,/$Y V7 +   ) 8 3 A *"2 2 7  +%    & ' ((   8 3 A *"2 2 7 11  7  2"* A 3 8   (( ' &    8 +1 7  2"* A 3 8 svu$;V9Se %&'&'&'&'45466303232&#"#0'66&'&'4>26&'07&&66766546646323276676".'&&#"'&&0#"'6767676676#"'&'&&76632767232%&465&'&'&'&74>32327667&.'&2332232767>6.'&'&7677&'632367676767>&66&&'&'&"&&&'&&'.'&5456""6&'&767>76326673272663'&'&'&"'466767>16%676322#0#&&#&#"%"#"'&&76&&'67>''&62'&1)("*)  1*  G   6 /O 9_+     G'<7K_(  4+'. D#"#31E3) ;D/&  i D 0y "FA/G  *<8?0d5A47. !,/52  "?9!$,&**#!" $!<"  3  19#      ! 8<     B    2 !v  #' (1 0B  '-5#)O#'!E '    #  V1$0&MT 4? #  s<:"|  D=@ 7DI014  /       6,+!   ?7     ->Q'+,(G@,    7 *  53  >v !#5!33r9T6666mmv\_ 6723237#5676755#5#&'6767&'275#4''67''673'5253'675#4#562326767676773367&'##&''3677#63#3#36##0#37#3736767767'&#"'706"1"56757&#"367&767655'025054#0#363675ow D54&'#67672'&5&''4&1'&'&'&'&5416767'&''&''#"#&'&'&'&5'&'0"&767654''#"'032676323232637676767676767632767677&'&54547672654&'&767654&"+kLLl+3(B0kkeO\     / [[ .        v*=!;M;!<*OpP +4CLkkLC33I 2BQ+kkW )!55!(               5!8SJi  hKS8!50DD0?v%-6?'6677'633"7.301&7&73"5"'667'64'7jKB5J"79)y"T+r8Fԃ :q](BGRuqQd8";a |"T,r` kJB#9()&"7B#])nr)B1Q:~JX"'Q'"7::>8 '''''77"cDcc]c]b&d' dDdc]c]b~(c'EwWf%"&&'#/&547'&&''&6675&''77>7&4>7763>226'%3276654&"V '!(+)]>< #'.Q$ =>V),(!& ]15#&#Q$ =>qO1+6[[&?< "'$.')+*X*-(NW'2#'0#')+*X '!)NW,7 N0@[[@>x%#%"'0547632"3Pt=tUX7ss<tQVrv+?[j&&553272232767#"#*#"&&553232232767#"#"#"'&".55033232232776746632"& )&#ryyq#&)jqri)&w~ ~v&)'*IJKI*?tS1w~ vStSU?tS1SC" o )( o "%# j ** j # -Y ** Y!8!B8! -!8!!<l&4Uj32345632!67632#"'&&'!4'3445632#"&546322 3##5##45&"##"&5467&5467232#"5#?"""7'&  .  ?" 77 44655*'7   $K#,'7 ; >d  D B]55>)7', [  > #6%"&4632!.!>"&'32>773dB\BB.4p;wnW9$@N@$P|Pn*#AN@$.AA]A3 C]K2%**\c51%))xrwv$[j777'264&"&#"326'77327032"#"#"''&'#"''&54767762'76303'&54>7632#"&|6JCa1F11F`'$ '=0 :G 'L0IN5 (7 &  %%1t+~!xA..A.,!1Mu  ]@&-91a~_'"  $"# O *Nam74663237632  !!%"'%672321&'&'&#"#67632767632767232'&'&#"'&'"#"%"'%52"'%67632767632767232462""26554&&&56737&##54##5325532655m  q, -aE TR gam  p, PgO -o  & T m  p, PguttR);;R;;3)/ E <    T D#FѢ hd  U h U  U huuu+:S::)):- /     ( Fv ),Mk}!!''!!#2##"''&##"##"50517633'"57'547632##"556.#"#32##"332##"##"554332332##"55432##"5'4##"##"5755633'#U{e4`/[I&4.(43 3H+''`-I$  /$^^<‘*/ 5t V7>"6*9%'#53232765&''#323#5767673337536"#"'&545454767675654'&#"#"#"#"'&541676323232677#"1"'&'#RTx     ! $" *K& "7:    #(1&" ,, +=C*) $d  *} ?&    +o><  >v"135:3.'|!I6'3>I\J|">I\/miq##"'.#"##"'&&"#0##"'&673127736767654'&#"7#"''&'&547666764&#"#"''&547632654&"z G5-*; '"3 < 3E3 = LR6*-'&*  9CaZG=6/!(79 E&5@FR'"|F $-" "--" :]% #!c  "'A/%  >0&AM`z%66767676654'&676#"&'&&#"##"#"&'46767667632&'&76""&&'&'&627671#"#&&54754630'2(r '$FJD'  TR891E&  i" 9J34 X    %$  "  (!6  *(  D1  U6  -f+f   ?F!%1;DHJNPTX\q7"'&5476632&&""&""&"3#53#7462#".'45462'32##"543'7'73#53#%26632#"&554626632232##"&5414642426426426424242$4242642%67"&&'63232232#"&"#"&'6323#"^ 1/aY CFGFFGFFFGFHrr((   #1"z~~r |x``  e    X  "  s  e  :Z$!q~ ~o@"#FFG#!? o}#F1121 |g41,.....lllQ .##;L"X9X=XAXE111?       1  a  q  y @7[;88(..(73/EwWf%"&&'#/&547'&&''&6675&''77>7&4>7763>226'%3276654&"V '!(+)]>< #'.Q$ =>V),(!& ]15#&#Q$ =>qO1+6[[&?< "'$.')+*X*-(NW'2#'0#')+*X '!)NW,7 N0@[[@>'G3#3#%&54545476323232#"#"#"7654'654'&#"#"#"32732>_RRUyyURRVxw%%&<<&$$&<<ƑEDXyz[UU[z xYS6A B7//7B A6.z-7K%30326654.''&&54663232&�#"#"#"#"&'%32653##"&'&&'#"/&547'&&''&46675&''77>37&4667763>26''327>54&"K8"! @@&H.-LT%%JDcN;a k)F12Q# & !  #&  !  $4%$ I=(@%/'5 J?FO<2)&l6J!6,v  #   #   #$G   %% >O0>HRVh|,.4>2"2664&5!%"#"'767#""'66.&547'654'73'27653#"&'7265##"&''324663232&#""#"#"#"&'732654.''.2326654&''&&5463232327'&&#"#"&&'''3012654.''&&463232&&#"1"#"#"#"&'%32653#"#"&'MuFFuuFEuڸllڸll>:m 8b  eߡ: 9oW@"=T"T="@$H N,"?'B@ GNn ZK.<'$<<*"@9RC/P?3( $,|/I,?C  WF*AQ=9. [Y 2#89L>(BI !B;VF4U ^$>*+HFuuFFuulڸllڸ$$0# #0\##³ںI$ 9J")"'TNGI6)2a!4#&% =5:A.'% !4 @+;D   8$,K99D  5.7  @kG)"/ @8>E4,%!A/A0&>?RH73#6767676363201#0"1"'&'&'&50413327676765454'&'&'�"1"'&'&'&&5055414'&'&'&#"#523276545541467676704747676763032##0#"'&'&505547676763032##0#"'&'&541'6767676541536767676=414767&'&544154'&'&&'7013203&#"1'ʉ- P   @       F           (A(       F>E.     "0 6 0" * 0 1  |     835 0 0 * 0 7 1 *>LE0%#"#"'&55654'&#"#533276504154763233#"#"323!32327654'5&54767&'&5455454'&#"1#532323273#&#"#"'#'#"&545&'&&#"'&&546325>'5&54676323#267654'654'&'&&&4767327454'632&#"&47672327654'632&#"&47673275&'632&#"E 1**3!^"!  1**2 ($4 +% 4$ %3  _   #/          /IN /$NJ/Q! $QQ! $Q/IN / NJ +'4$  *%3   * 4$* &8$ !    (5  5 !  5   }kQ23276765456767676763233#"#"3233#"#"'&'&'&545454'&'�"#!"#"#"##5323276565&54767&'&'&545654'&#"##53232303}  )  )  L((A =32#'16 L = --*)**-- = L 61'#34 = A((zh-7K%30326654.''&&54663232&�#"#"#"#"&'%32653##"&'K8"! @@&H.-LT%%JD/A&;a k)F12Q$ I=(@%/'5 I@#9%<2)&l6J!6,vN %' f >✼wd>w  -@FL\cpw -4GKW`frv|     ! 1 `   # + 0 > H L P d q   ) - 2 I O U Y p % . 5 < K P W b i y    ) - 3 < @ R  #)04:@EIT`k  )-17=FKOZ^dnrv  $,04BGL_cgr!&159=OY]fjosy $-Eioy} '+/3HQX^!%)-17;?CMSX_y,26@DJN\`fqw{3:@DOSY`dhq  &*17=GQhrx '-3<ELT^flu}KQW\`fjptygntz :EKR %+15;?EU]dhlrv &*/49AE]sw} *:?HY_v}   / 5 ; D M U Z ^ d j p z !""" """"-"3"7";"A"E"M"S"]"a"z"~""""""#&#+#/#3#7#;#?#C#V#Z#a#v#z#~#####################$$$$$*$2$8$C$I$Q$j$q$x$|$$$$$$%% %%(%1%5%<%B%P%T%g%s%z%~%%%%%%%%& &&&&$&+&6&@&M&_&c&i&q&u&&&&&&&&&''''"'>'N'T'X'\'b'j'y'''''''''((( ((((%(,(4(:(Q(W(a(f(p(t(~((((((((((((()) ))!)%)));)?)E)I)M)U)Y)])e)q)w)))))))))))))))))))*****$*)*1*5*9*=*H*M*S*Z*************+++++2+u+z++++++++++++++,,$,(,1,5,9,C,N,a,e,l,p,t,x,,,,,,,,----*-2-?-Y-_---------... ....+.;.Q.Y.`.f.m...............///#/'/0/D/K/O/\/h////////////0 0000#0*010;0B0G0n0r0{0000000000000000011 11110171H1O1S1\1d1k1q1u111111111122 2#222;2F2Y2]2c2i2z222222222222222222333333&3*3_3i3p3u3{333333333333344 4Y4a4h4m4t4z44444444444444444455 555#5)50575K5O5Y5_5j5n5r5v5555566 6666$6-636<6L6T6X6\6`6o6s66666666667777'7+7C7G7S7W7^7b7m7s777777788888%818?8G888888888888899 9999%90969>9B9J9V9Z9^9b9f9999999999: :::":&:-:3:J:N:U:[:t:x:|:::::::::::;;;!;%;3;7;;;?;C;W;_;;;;;;;;;;;;;;;;<<"<1<9>>>>!>(>,>2>;>?>C>M>Q>_>e>i>v>>>>>>>>>>>>??????"?*?0?A?K?f?j?n?r?{?????????????@@ @@@"@(@0@9@?@C@G@K@T"6&"'&'&'&'&#""1"'&'&541454765654"54'&"'&547654'&'47676'&'&'&'&'&#"'&54'&'&'&#"1"321'&'"766''"03"'&'&1&5&'&&'&'&'&'&"#&'&5474547654'&5445&#5&'&'&'&'4546725&'&'&'&'4547654##'"#"5436767"''01'4'&"#"'&"4"'&'&'&#"'&'&'&'&'&'&54767676746547676336767276'&'&'&''#&'&'47667672'&'&'&'"32366243276323766763212476364763676765&'&'&''45476767654'&'&&'&#"'&765454'&'&#"567674#"&747676767676'&#"##"#&5676727632765&'&'&'&76326365&'&'&'"567632303276'&'&'"465&'&'&'&3&767420767656766'&'47654"427054'&'&'.#&'&'&'&'"'&&"'&'&'454767655676#&'&'&"&#""7654'&"'&5477'"#"#"'&#"'67676'"#"#54767&3236767276767676763234276&763234##"#&36"&#&'&76'&623676763276676676'&'&#"76767614'&432767676367676&54'0454"32127054'&76762"4#"01'&'4'&'&#"367632120"''"7676'&545454'&541414742776376367672#"1#"5432'&43223277'&6676767623#"5&&32764'&'476762#"225&'&3#&'&"76'41476'&'&'"7627414'&76276323767232"76#276'53&#"'&"332'&'4'&'&'&'&'&'&'&#"327622223232323676'&6367676767676#4"'&6#"'"133677#"'&7673&'&'&'&'&&&7674#"4#"#"1"'7367&'&'&'676720&''"#"5&&&'&'&#&'&#"'&#&'&545676326'�#'&'"&#"&54"4'&676"5054'&#"#&#&24"7454'&6'&76&25&"&2'0"167674#"&24"67674#"7654'&3177&#"&2'&"'276541"225&#"27654##"224"2''24#"254'&"6'6&327414'"#""7654'&34"424545&"6%656"30'676"7674&#'654'&'.1036265&"7"70216764'&'&762''&632325&'65&'&"&#"#63$24"65654'65622'""#!25&'&&"422&"%25&'6'&#""2'4"24"3%64#32%676&#"225&'70545&##"%24'&5456'""#256'&5054#"27327654'&25""#24"445254'&%274545&0224"%674#"0%2'&'62'46265&#"#&'&'"3223'&"17&2321276'672665463676&#"'&#"'&'&7'"56&&30762&"44#"37"762#2547627674"224"25&5&#54&#224"25&'&#"32%276"#36765&324&'"762'&64#"7254'&%445276740%654&24"24"674#"'&#"323307654'&527674#"#"7674#"32%0&5&'&6&'765&76&#"324&"7654'"#"77$2&024"7&'&'"%676&#"&%65054"325&767'1'"7655&654#"%676'&#"024"64#2571654505403324"37654'24"47654'�#5676""25474#&#"16226#"765054"25&'%6&#"244'4'"%6545&%676"&2'&546'&'10167677#"&56'"76767476"76276745576&#"&71'#"4#6"743237"4214&%650547674&7'72'4"324#137&#"32'4#"#5'33254"3676"76"7225&'24"254'&24'&%650&5476545�76'&43265&32543672'&'&"#&224"2'4"%6'&322'&127654'""#25'454'&76#'"224"254'2'4'"224"67654"74&276322325632'57654'&7654"6"7'&#"6"4264"676'&"704524""2'4"4454'224"6765454'2'&1"'3224"24"27654'&$24"674'&&322'451254'&033274"%64#24"225&'04"0%24"324&3 24"24"332&324'24"%66'&%6545&#"%254&30327674#"#"'41476'&#"#56765454#230322224"24"276&224"$24'4#"254'276505&'&0'&2254'&2'&#"3626'"254'!6'24"24"24"64'!4545&1224"24"765&'224" 24"254'25&'6&1224224"6'&'&$&24"36'"'524" 24"676765&%67476&"#"'#27&765414632766&"74224"27641"#"&24"24"3224"25&'24'&'"24"24"376541432&#"64"224"254'!276416"4225&'&"24"24"76545454'&%64#64#632'&#"#'"1 24" 24"27674#"65414#"2:6"2&"24"27441"24"24"254'2&6545&'224"6''&24"276541&'&!7&#"1365&24"6&7654'&5&541414'&367"'&547'&'&#"#26"4457676&#""224"24"24"27674'&%454'&$24"%676224"44526"24"2'4"767454'24"04"324"361&"%24"!2&'&'"6767676'&&''767'&'"%24"34454452'&#"304"345454'"#""2324' 254'24"24"6&5&'"32'&76"7�#"3676276'&7#'&3'&&'"1"323"'"427"7432124"27674&5"420024"24"23276'"&&'&'&'4#'&505414'&63'"42'"420&"42#&'500&"427"42454524"254'&3$24"24'&24"24"3364'27654454'&$24"24"24" 65054'&"#&6'&505664'364"2&767374#"'&#&'&3#"'45476#""6762'&545#24"24"%27674'13676724#&'&'&"'&50561"732'5&"422'&"2764'&"#'&#274545&"%6'"1"224"24" 24"24"24"2'0"124"$24"24"2&'&#"2%7"#"164'26#"3267674#"#2672''%445"21276&324"304"1764'7&5416&#"3&"4276545&"0725&'24"2476#"324"24"4452'4"%7"#"#2327674"#"'&'&36767"#"#"#"''"'&#"6#"763:'4"24"%6325&&24"2'""#24"04'&'.#&24"654'3654552$254'24"24"654#"2'&#"324"3225&'!27654#"3744527614""242'&"2'&"24"%6554#364'2'&#4'4'"2'4"24"&'&"24"!24"076656'&332577414'&54'�#"'&#"3254"3654'24"654436224"254'25""#24"24"25354276767422'&#"24"24"24"254"!7""#324"$26"24"324"3276#325'026#"22'4"04"1224"%44524"6545&"026541&7505&#12'&"%24"32'&324"3224"25&"7652'""#25'''3656"767&'&''630''&54724"3%254'&224"24"2'&"25&'7672&'&#"##6454'454767676727676'&7627'2#"7654'&&#"&56#56#"'&7676'076"76764676#"76#&#0'76'&"#"'&'&76327477'&5'"6765"77'&576323276#"5""'37""'5547677676323367676#"'"76767276773''76"425567672#4776"4250474302"42'0676707"567'476770"42""42"47630'77"476307"42#'"42#'"42#5"42#''"42##3"7672'045632'76722"42'"7673"7672'76'"76767"42#'4777"4275676&"422"42'"742&"422"42""42"56#"7632"'74#0"42'"760&467232"7627"7632''232676767674545&'454'&547454'&54'&"56##"&#&77"42'"427'7&"42#04212"42'477"42'77"427"427'7224"445445!67654#"1&224"24"3676"'&224"2'4"24"7654'&56524"67676&23277632767423745667''6&#674'&"##"'&"#"'&#"3232767674'416767&'&'&36"337&162"6"762742#'60#''66"742'56'5'"5223'6'"'&234"422"7626"7426"4224"7674%54'&'#767&#"2'0"127414"0'654#"2224"372764#"3724"3724"3764""5454""'4&#033543267454'&"7642765"677'&''##"#1"5&2'&413232724"2'4456'&'&5&5'3'&'72'&#"676&'"2254#"24"27441"224"24"24"%7&#"654'4''24"676'&"4##224"24"24"!2541&"03764'25&'&"324"3225&"24"654'24"24#"2274#&54&#32274&224'&24"24"24"1224"!27674""#"#1%6767654##"'&624"07445!24"325&'0122764#"24"2'4"24"24"276#&#&4545&54767654#74#7456'"'&&'#"#"7'&540254'24"44164"74412''124"6767"#"'&67'&'&54'&72327#"&24"24'24"324"324"324"324"$64656#322&"%24"724"$24"24"2'&'"24"44524"24"24"124"1332'4'"06"42654'&27654"""3227454'&'"7664#274&"32'&7654'&03324"7676''&'&7'7632'"52234"4"!24"324"'654&2741625'327677654"6&25&'&2'4"225&'44567#433'&'&7'32324""24"2'&"7476745624"24"65414'&'&2'773304"17045326#"3326#"3324"303'44524"650& 2'4"$2'4"27441"7640767654'&6"4224"6'&547#254'24"36766327676767676'4'"'&#4"'&"5436'&505414'&541'#"4#"'&367674&'&7#&'&54"76&5464##'&'&2#"'&54"'&'&'&'&'&'&#&7623273277607632'&'&276327276763173607654'&5637614'&5417&""65&'&54762773:#0#"3232&76"7021&"426"42&"42"'405&"42'&'&5476'04"3"42"42'"'72"42"'4545'"46#3045632&"426545&'&'&54766$24"27441"225&'24"05'76'0#"776#05&'&45054'"&'&'&'&'&'&''54#'4145&'&'&'&'&'"347676720764#"760552'50''7&"42&"42""42&"42&"42''76764'&7#&224"445"2656547655024"24"2'0"1!24"3324"3%44527624"64$24"24""24"674#2026'&"24"61&"$24"24"254'24"76&726"7021045!04"1224"24"00763&"276767254'"#"24"002'4"74545&"%24"325&'&36541476'&547##"5324'&!61&"224"%3276'&'"0%676&23276&27674#"#36#0#"'&50654'&"#327$24"6767676'&#&#71"24"24"3224'&#" 24"24"676767414326'&76747'676726"422745" 25&'%6'&545524"6"76'&2'6'&'"224"1$24"254#03365414'&327614&##3676'&505#7'&"062767463542'�#%4545414/#37''704"1725&'&50654'4#"3%24"%4545&224"254"3%674%24"1376543254'""#0225&'"'&545524"2'4"676#$24"254'&54414'&0133212541&"0;67674#"##"1'72"#"'#'24&322'&"6541%27054'&7654'&#%64'%254#"7#332763235214#765624"2'&'&'4'&#&21432'"7454'&432&"&3&"424"42'"422'4"327454'&3325&'&624"24" 24"24"7637&#327441"36767&'7654'&57654'&'&&"2327#""""#&232321''66'&76654"24"2'4"656##"'24"764'24"325&'$54424"32561764'&"24"32746#"264"!6556"422764#"'276#&'&24"326"332765"326&'24"%24"24"02746#"325476&#"5&5476""'274545&'&24"767'06#"1!254&327624'&323224"24"676545&#"'&6"422'""#24" 24"%>50224"24"2''06'&3224"32764'&#2'4"24"30''0$24"2'4"24"6505&#"764#24"27454'&24"3657%67>'&77&#"24"3724"3724"362'&"24"3$2'4"454524"454'&'&#"17445%674#224"24"24"454"0744524"%2'&"305&'&'&'&'&#"#0#"132676232'624"65654'&&&26#"!7&#"#324"332'&7654'&24'&224"%654'2'&"64'6&%67676703274#"32'4"32'4"32'&'"2'&6'&'&"241'6767&'"54&"76#"30#"'&245676727"#"#"#"#&"27'624"022'&"7654'&''&"7'6&"4204"132'&#"37&#"32&3327441"3250'4#"327641"'70#0#2'&'"24"376765&'&'&#"36732%2&3224"654'&224"24"6&2'4#0650'4&%677654#"5&24"6&:4""24"24"3414'&24" 24"427454'&2&"24"27'5''&763232'&'"'&767627655'#"&767'"'121#"'&223676763032$2'4"2765414'1"7624"!2765"703276'&"7"'5#34545&"76545445&'&#"32362'&"6767276545765&#""%25&##"727441"706#"3$24"24"44576323276'""#6'&'&&'&#"'&3236023276722176074''7&"702324"6'&''241"7654524"34545""1720'&'&'&3%663233'&77677#765""337&#"1254"2565"765454'&'&'6&#"376545&&#"7654'&&24""2'&"24"'&'"227054'44524"!24'&375""#336'&77654'4"724"2'&"%67677"#"'#'&##"##&#"3237#&"224"24"21&'&36505&#&2'&6326'&"24"2674#"3'27674#"%2763676'"76541&3&5&#"###"#&033363&"32&22'4"24"24"25&'&327654'&"54&#"'&23262367632737674"$2'4"24"24"25&'&322'&"&24"66#324"0324"0324"07656230%24"076&3654"4#"'6'"176'&332331332724"261&&22'&"&2'4"656#"'&3024"256#"%7&#"137&#"#224"24"24"256"#%6'72126#"222'4"4"7&#"76541&16"42676&6224"24"322&'2'&"76''0'&'0227&5%676&#""572'4"224"2745"!25&''2'&#"$2'4"24"2'&'&'4"4#'&547654'367'"7654"'&'45456&33436333&"42'"422'05472541&%2'4"77&#"2'4"3676&!254&3%24"3276545'654''"#76'&&"3272'&&366#"764'&547#7624"254'2'4"%676'&###"##3276&70545&#"324"224"6&76764#"6"4224"0025&'674#"'&3024"44527054"1%64#676"$24"25&'&24"6767322'&4#"13276#0#"24"%7&"24"2'&"224"25472'&'&5476&132"056"623232'"6563004"0764#2'4"$2'4"7""#3676'776674'0#"#0&#"6"4225&'24"345054'&327441"225&'254'&#"%63&"3361&"224"7454'&'"664'27454'&5456"'&'&'&'476'&'&"61##"32325&'454763%256#"725&"7&"24"125&'76545&#'"6"426"4224&24"70452'4'&054'&!24"'24"364'7&#"676&%70#"#24"324"324"325&'&%67670""677#"65414&26#"7&#"#22'""#2'4#"%2'4'"76545&54#&24"25641"25&'2'&'&'&24"24"24"6767&#&#"224"7"#"1$24"3254&66'&''0325&''&'&'&'&56#'077#&'&#"""32327676374'3'&'&33'&'&'&''02554'&24"24"7&'&324"365&'&%24"674#'367676#"#32'&'"$24"24"24"#274545&#"#"224"&'&'&332'67627'&#"'&&3677654&1476&"422"42""426"4265454'0324"0676#"'&"'%66'&'&'&'&3256#"32'&54#"766'&#"24'&'#424"04'&325&'&'214"622'4'"#"#22&"2760"24"27414'&2224'&#4'&'&'&'&'&'&7'"#6"'&#"3276323632323224"374412'0"1274545&3725&#&'&54#"#&72'4"%26276545&#"#"'&326#"74'&"24"324"%2&254'&"727#54'&2746#"7676545454763267414'&&'""#3236&2&367672&76'&764'%2'&'&65&'&'&'&&0374#"224" 24"7414'&227454'&24"24"2'4"%656'&'&'&'&'&624"44524"025&'%26&3274'"#"2'0"1%676#224"6'&#454'&624"24" 24"24"64#4&7#'&740"#"#&5456"'&6'&'&&5416&#&'&'&'&'&'&#"'&'&36'&#"#654454'&24"'2&727&#"32741224"!7&#"32'&'&#27674#"372'&'"224"24"!7&#"24'&0&&'&'&'&'&3&"4225&'&24"654'&6'&#""7&"4224"67'$2'4"05&'&'"454'&'&''&'&'"13232674'&"632326"25&'&3'&2%276#"32327'"#32!24"3324"336762626"&33222'&"24"6767674$24"24"24"24"265'"&36&"'&'&'&'&'45&'&'&'02254'24"2'&"327676&#""'&'4324#"72764#"224" 24"2'4"274#25&'&5&&#&''1224"26"24"#254'&#"%7&'&#2'&35&'&'&'&'&'&&'&&76#'61'&'&3'&''676727"'46'"423&'&5"42'"42254'24"3&24"674"76545& 24"24"25&''24"25&'&32376&76&$24"24"6&$24'&276&0367674#$24"277%74#"'7&"24"22'&"324"304'&#"%67&'&'&'276505&'"2337&#"2&""%25&"2&324"%3'""#25&'6545524"337&#"1224"032547&53$254'24"22'4"675'&%24"30&#"2&#"%2&''24"'2'&'&'423'&'&'&5&5476"#"'&#"13#&!24'&''&25&'25&'24"25&'&24"654'2745"224"24"26232'&6"427654'0#"7624"$24"274&"#"''67'&32$24"25&'%67#"'&#"#32 24"676432&&6'&#""24"24"654'7&#"$24"054'&'&'&725&'&'4&'&423&'&'7265&#&24"2'4"6&$24"2'&#"04"1!3276765'"%2&5&'&26676767274545&'&""24"224"24"26#"30'27641"6262424"24"2'&'"!4545&&"76505&#764656545465414'&#"'&6&6&24"!25&'&'04'&'&2'&'&'&2'4"$2'&#"'2'&'"767650#&345452'""#3%614"6&2'0"1$24"&24""24"'24'&'"                              "        $#                          )                      B#4 7+Xtk  7"}>d 6/3t- 5p%  um}y[O _Y F %?A=    &m6 |) $ )   r5%*,IvR+  _m B jR6l& BkmFK PF Ipq  h 4; )|$=D 3H< l,_5!t    B` 0I3'N d  ZP t  >}(H!  (G6U UF)Q  f8%30L v^LA4sG/u-y6 41 ;)"  + &06qKF9 yQ*   +< X                   )1>6(" 4"4#   U \.  ++G    " ?SM_q#&         "");8d$9~l Z0Cr- @Qf#l  n k   O B !*W 7P 3b   *!  :4 :1[Y!k   'S*E g t Iy &9ZN$e14-le'         >3 <j.0X=  CU     1d f }Pi g,  B K i=.P  -&    U4   og l j: *? Y~;47Ip0 W7C^ _I&L )&g'dNtg  VKl Dm  Jp  () H 'O 5XBr  E-<     E  *}*$9 T(=UOz    a)  7+eC%     N54S!,Mp4T  A0 1g =O0  Xyf>."j gi~ZL5  , J&;oV1@ U> ? i=Z;$9C V/" y\93 &x3   |8M T de X EP BU-h( ` D  `Rnre  /V           (xt h(e  -   'd;     9Vb#Vf~  e]a^#1[2+dTry  =~9  9o   4Z7 ' <^E 0e :q     qc\;"   W <6e]       : L;  pp       VT yDU \Q w   +W~eZmK$     PJ1 I p   vF%<     0HN P  a6 - B~  2yr                                          ""                                                              +/                                                  %           . _     O         :>                   Y          S                  @                           "                        &                   ,2        #"                      B                     >'7537#57#5''7'77zz|"B.B//Waaad"ad#d`zz#BBT2aa"ad dd da>X9%#654'&#"#0#4#"#336632323266323122" $s.#_99\g>> =6) 6AI6@I]1:B77C ub74547676763232323276767673302127632320#&'&'&'&#"##"#"'&'&'&'##"##"#"2$$667654'&'&'&'&'&$#"32$76767676767676767654&$$#"$7"332>54$$#>%(<'% ' n&$  $'m *آ/>)&H$-1_Gh?ݛVQhl/o ZV7YdWr*WD+$1!vVe'&'&'&#"32767654'422#"'&'&&'&545454767667262164'&'&"T(2E3zgXWFFJS/-v>6`</,vla-!iM('^_&&|iVL` &op*)>X-?212767..'2677632376767&&' JH>""  $ʺ#  &*"DG9R.01-U:1$82X-) 3 1:96 )7 GK,WY,MtRFGKC?v 877&32#"&'?532664&&"&54>3lV22yXuEEuXb= 4RdbbǨb!EuYqV22 FuuFQG =EbƩbbc(' BFYuE?#44k{632632#!#"'&'&'454767&547676326&'"#"323!6767454'&'67654'&'&#"&'&'&#"632"&5466+(&3-// 0)/  ;()="& >+ ' *<5(#6!<(  %6% /#2/1 ,"-9) !9 48 -( 2+*#(*  /   &&>v !!!!!!!!!!!!!!!!Z-)}k %1?KWcs+;GS_kw32##"&4632##"&514632##"&46#32##"&46#32##"&514632##"&4632##"&467!2#!"&46#321##"&514632##"&4632##"&4632##"&5146321##"&514632##"&46#32##"&5146!32##"&46!2#!"&46#32##"&46#32##"&46%321##"&5146!32##"&5146!2#!"&46#32##"&46321##"&514632##"&46!2#!"&46!32##"&4632##"&46332##"&46'32##"&4632##"&4632##"&467321##"&514632##"&4632##"&46!2#!"&46#32##"&4632##"&46!32##"&5146321##"&514632##"&46# #   l l Ϗ  Ϗ      f  k k # # # # G G G G $ $ Ϗ  I    k k d$  $ I    sA  d$ $     [ T   G G G G k k k k +G G G G +$ $ $ $ A  G G &    k k k k                           S      S                         H            G   f       G   H              H   v[(%654'&#"323%#"''&5476323   5D  +( `   ?: %!'!'!!%!'!'&477'7'7D,,DpD,-  --l@@@-    --6)JNQ!632##323276545454'&#"##'3'323276545454'&#"##'#'^ dIB!;Ry""y4' ""yE3l9FLd HA>$B.#"H@V#"G4'Q 4&#"#"#336763232#54#"1"#33>32232"JJ  uJ@!JJ &l9 %(=٢X#y}kQ&#"#"#336763632#5654'&#"#"01#54#0#"#3366302323266322327"II  4I I?II4 7!j %1?Ϧ "U"10"|a6&#"#"#336763232#"#"55#53573#3232127"FF  Z33FJJ  Y &4@ V7G[7 vf'/=a#!".=&55675463!2!>'73'7!"&463!23!&&5533332776332323665M   E!u V% HM66MH# ) q & #n K !   K!!XrKzGQ""!  v6MM6vs# 2 2 #Iv !!%%'#'737%vJJ 2t55^4ii4\,DfD/ZZ^ZZ^v !7!'!'77!!7!'%'[獌> .<LT\d67655>732##"554376''6554'&55467'7377"&462"&462"&4620O#  K}R>h+d ppn8h>R}K #O0s'y u%tFC<<BA<BA@#DCFtgu z٪ ;:/`/@7'&'&7663276766.'&66''6'&%&54632''6'&'&'4763276766.%'&67276&##"'"'476767276676#"7676&&767'476767632632327667>676766'&#"01&7632%&766766767>7>72>766323'&76676"&676'&627675676'&$2( !  1   !     )  " :F.: .( p  I A-)("' &=; =@  ddmk$ 2  + ) 4z TRpm 4 B)%s  !#  ! <$   )&!! %-+ # "< , '0 "!  "#    <Fa ;CJ1%pH) .(NFL!$\V  $V[d EO :D "'= #!& 8; @N#" A@8 >$&%--@I>v2".466&'&#"#"'&&76767654'&#"6676&''&5476320#"'476545&&#"#176767767454&'"#"'7>36032366545&&uFFuuFFuE+.'6'   +&>*  ! !  ( O/H 3<        -FuuFFuu\69F, *    5 ((  (N][ @\tM*   &#   >3#3#%3#535#7%462#"&&>f==fff==T6L55&*&&&WWW$33H3'>tn#"#"'&'&54147654547632276765054'&'&'&'&5454414763230232#''&'&##0#"1%0"#"/&''&'&'&'&''732332363232#0##"767747674'&'&/&5?232.'3>#.>  !#  # ! /   #  A L *    3  p  i[Jaqul][^wr" i($! * '! 1'% !%!  h:#o:  ;A!,2C :;@Y&4jI0L+ >8S*B0@Y&4j>E'73!73#!'#owLx=uX!bpqpuLz?!gg ?O!BXgy%8%&&7677#"765736&&54743667>77?#"&''5&547632#"'?67671&27653#"&'7265#0#"&''32463632&#"#"#"&'772654.''&&232654&''&&5466322327'&&#"#"#"&'''3212654&''&&463232&�#&#"#"#"&''32653#"#"&'96! ":    }8J:}}ihJJٕK#(W xx { \G~4g4[B::b J* ;&>< BJh UH,C8!97- =5$?),K;1& 43uDX=? SB(>L96# + W/>-#!$56H;%?F >9RB1Q YH=*D& pBC1 6/!#"#"  Y:!DMMJ st u G`F>h'G ' $PKD84'0N0>"#" 91%5,$#  9F?7A 5#)G75A   2+ `8p.##3'53#"#7!!53#5!#&&##32653#4&##32673"#53'323&#"5327677'#5327#7&#"#5327#"#"3"b$C``C$ % // $0EE7)=%PO 40 Zc+ ;<%BA 40  Kp+   iii/'u{/6ojw\We > #)''775!!!%!3#75#3#75##%5.=>w NVW+W-D;==++'q+WW+WW,>v###5'3#3#!#53#53ǜmYYlYYKf(faWbabvLt%#"""#"'&'""1&&5467547673065267326555313'654'4'&'&'6706167676265&'#".5663267&&54667&'"&5567&#"23221272636726636766367276767676767676%2"&546626654&"8(       zC:0;,'7;0R%%tt&-D/+-mkLT7$-%617/2LD`     j!!.!  W:/  {M0&*6&c&Cf     U   5@5   PrrP).B)\pIh?$+?'/" D2CY~~YC-y;_B . Y  nzr#""#"'&&547&546766323232#"#"#"#"'&'##"#"'&5454547636327332676545454&&'&#""#"32327.#"#"036767_hr6=?87T h/4I!\3D(?*,EEQH/ KA %@3&WtG/d3-`VPj '9--[1!* A)e2L U;:A^,uB rY%* &.2CmVV7*"$#HY %D:M93l @q*U! FDRq$*:>Vx!#3!5!#535##5!!%53!53&&'#/&547'&&''&46675&''77>7&&4>77623>26'%3276654&">i>}xiAA !  F/,  = -/@F/, "= -/ ,!(D`D, ^i}>/niW/. $# C: B   B !; A":$0DD0>v!#3!5!#535##5!!%53!53>i>}xi, ^i}>/ni Fv ),Mx!!''!!#2##"''&##"##"50517633'"57'547632##"556.#"#20"3##"''4"##"503705'43322576;2##"332##"##"554332332##"554#U{e4`/[I&4.(43   ! 0H+''`-I/$^^<‘*/ EI..EJ--5t> .GX3>7632323"#667&&%3"#5#0#6703673#45&&'05&%3213"#4>G++  9?NT>91l Mw,I'(,, 7;,v ji++  NTl vvH('vvJK 8;}vv`` v>!vU  )29f67&&'6%644467&&667&74667&5&'&&'&>56566767667465&'&&'44567&7&&'4677566767.'&'6.'&&'674456'442&'6767667&'4ъ*3/+2"36ttK73"//0&K* 4~|!6%&'-nv)<8 94. <7):'&$(ci&3"N>M  5KN>N  '1 tt+ 8 MM:+kge3uoBC8eML!zd,jk/h#OJHqHu- ,Y>@V0 $kl KI%%NN@ %(x5@E)&?- z!WWWWt .?)&}#f27; e+"+MM** OO:oML/%/*U06 -i BCpHJ>vEX_fv#JZi7&663&63264.54654&&7622"#"&#"#"&6#"&&'>'"&766'67>'"'&547>&&5466774632#"&&74>32#"&462"&462".76232>2###"'&'.505041467>3030212327%654�#"0#".541414>32121211#"113277%"76554&'.3@3* 2B=   %0 8]|'4.10 8>'1@'-U #1$'1#'(   2F22F ## !L++L!L&+   !?(9 D='aq;Y0  ' ( V==V>J8''8(Q '""& JOVVOR>=D!!?'aq;Y<(9 D=>@XmĊXm?PAsUUs@@sU9k\&:@q\sG2 /DD \sG2!/>84g%#"'&#"#"'&#"#0"1"##6532763232212276323237"#"'&#"#"'&#""#"##5327632322322763232757"#"'&#"#"'&#"#""1"#5327632322122763232 @7&--&9AB8$+*#6@*#7?@6&Y'8BA8&+ @7&--&8BB8$+*#6?*#7@@6&Y'8BA8&+ @7',-&9BA8%**$6?*#6??6&Y'8BA8&+e*% !! <!!!!5 !N!! 5!! O!!!!>W;%"&557463264&#!&#""&5450&&566321!232"%454632323!03264&#"&&545546372##"#"323232&&541%Q:> %W=5KG48H  !8OM75L"9#9P,I&!j=VK55N;  !&O86O0/&L5#;"Q9?v477373737537533#3#3!!!!!!!#5#53m77676686776H6~67Xq66ipmmm7;77 7|6F68668676776nnnn}vk)Gd%#"''&547'#"''&54677632632%7632654''&#"327'&54654''&#"#"''327N")0M/L/l")0M/l#L/ #l3!*m3 ! )3 #  ! 3!j&0$? =&02& <&0$? <'05( = .!) 3 (+ 3(.  +(>v /9B[d!!32#''#&''67&&''367#53533!!"&'66.##>73##&'##67667o8"6J6(A=* ,+)i5T""  m 8 ( z 0$! "$M:+;9- *02/;"77:"Q"h_  56HO  <<V,/N #'+/37;?!!!!5!535!5!5!5!5!5353#5353535353I IyLE3}ȇ>N;;;aQSY [Q QQ PfQ vb?_u}!2Ap67&'654'&'&'667&&&'6767676767&5476676677675&'&75676767454'654'%3267&'67&54&'4632#&5465&54654'67676'6323#"'4547454>5&67>6323&'76323&#"4>'660.46632""#667#>676#0#"#&'&547.'&46653232767232#6763267#"3767&&#" ) &+eD: /+)86 9N*_s "!*H<& ) H0&* 68)<  ;   M , 8- 1  _CMKA !%  )%14   )' ' k', + $ ,$ & 8 "$ -"+ !% # ( .1ON ), * Gb)ifi^,53 M #H ;@ GE 5F on"HO$6G * +) NO1.!Q!+ M 35!!C+ (5  +  D ,/,   g,*         "  m\ $;?   -,DIe "   &>weh{ -AW4632767654'&'"#"3276&7672#"'&'&5476&'&54767632#"'&'&'&'4&&27667654'&'&#"076767032327654'&##"'&547632#"'&&'&5474'5454'&'"#"00326232767654'&'&#"327676'&5672#"'&'&5473654'&#"#'&5416767632#"'&'&'&5454'&'&741476327632"#"'&'4546212367&'&#"#'&'4547632#"2##"&&'&'6627665654'&&###"&&'&'66702'".'67>67674&&'&7'&'67667#"'&&54567666767454'&12#"#&&545676%!#"##1!&'&'&'567632334767663!#"#"#"'&'4'&'!&54 5%/2>' ,  + / ' *9,/%$D* 2!=@VJ@B>!1 *E$%/,9+ 0 -    + &?2/$5 AJV  5%/2>' ,   + / +8-.%#E* 1!=AWI@M %  ,T$  , , % [  &                (  5(   =(  i."1 E234*6  F/'7P 1 7') '["( 64%7 1$ # +  ) 7$("5/2R== 2!m5")#9 )  , #   $2 7%$# ("4 ''R3."'%$%7 2%  # ,  )  9#("5 .3Q'' 3   }|   }l ||  +       ol qn kp nq  pk qn1E  *$-,E @,&t$  '7'7 )>v #)/5;AGMTZ`flrx~0>'27#&'7'67&'7%'67&'7%'67&'7%'67&'7%'67&'7%'67&'7%'67&'7%'67&'7%&'7%#67%&'7%'67%&'7%'677'677'677'67%#7'&'7'67%&'7'677&'5'674662"&264&"6462"#5333#5#"#"&5531323265533#"&&'#"&&5454676766554#"#6323323'5323267:q    |  >       J   U   L   *     %  )&pFqZZZ9Q::Q9Q::Ql"       #"=;,             "   , 5 < @ D0.1,  #               ZZZZQ99Q:9Q::Q9lxn W   3+X 6 >6Qv3>2632#"'.'#"'#&$'6$667632.'&#"#"'32676&&'67654'&#"5&&'53232667654.'&#"327.9   $ M;"& E % :5;U#40  #,,+(   D$.- (!C!27LgA c  & !   &j-4 1%z21P.K(   @2D6 5   l#64 BS1  3) ?$(G25;X%   W]82>lY@`7>76302"2'&547667656545&'&'4547670327&'&'&'&'32367"'676765#"'&'466766767654'&&'&54767>732767654'676545&'.'&'6767654'4'&'&'"##"#"&&'4&54&5"'327232766'&#"#'&'6767654'&'&'&'&#"'"'767'&'&'&'&#"&'&#"#0"#""#&&'&5476763>'#"#&'&#"#"#"7632327367.'&#"'''7'7'7'?777/''377777&'&'&5476763232322327#&#"#23276676323632636'4&&546'&'#0#6545&'&&'&'&5450.'&'676567654'&'&54767323"#"'&&'&'&'&&''667#&&'30327654'03220454'&'&454746767632&'"#"#67067454'&#"263>067#&#"#"#&'.'67&'&""#323263632"#"'"23263012&#674'&'&'445&'&'&5454>7#"&74545&&'&&4>165054'&'&&'&'&547665654'&&"'&&5&'&&'&6'.'&6'&54567'&547654'&#&>766766766&76767636323276676732766'&'&76676'4'&'&'6327654'4&'&'&'#"'&'2766763232&'577''3'7'7'7'7'7/''7773''&'&'.'&'&#"632"1'6'56767654'""""676676676465663"#232765"#"'&'&'63233232>54'6324'&'&'&'&#"67632"&7032323&'.'&'#&6477&7#2>45&'&545674667632&'&#"&5476323032327&'.'&'&'&#"#"32765414'2324'&'&'&#"'&#"&76766766'&'&'&5476763232322327#&2>766763221632676326765054'&'&'&''&52>7676327632#"#"#"'&&'&'&'2323272630##"#3276727"#"'&'&'&'&'"#"#&'&'&'&61.54547667&'&&'&'&'&5&547667654'&'&'45476674'&"'&4&&'&'&547>76'&".&'.'&&'&545'&'&547")                       *$ $      5   !$ # ( Y"=0   "5/6+(C    "   /"(    (     @')5*/  .$%B#V $             0           #              ((    0        $"#%    # 0* #)       #            <          EO          ;      C',6/5"   0=" Y ( # $! 5    $ $*                        !!K"!                       &2&      )"   d&1  5+0C36   E  /!      -31@ .)4  7!(#d       3                                      =-   G7       #(/2$ $0%                :      2       D s  `             63C0+6 2&d   #)     %2&                         !/.3!1R/1: `q!3#5!%"&&''/&547'&&''&46675&''77>7&66776>6'%3276654&&#"232#""1"#"'7320326505054&#"1"1"3'3&546766322"&4632(K9 =)' 5')9  =(( 5') 3 #/*;f.YK -60.}Eu[6GVooput60/|FO.A// ol$-)' 93 9)'93 9s# 3.;*q-KY.F|/05H71oooF|/05 ..A.W `qtx!3#5!%"&&''/&547'&&''&46675&''77>7&66776>6'%3276654&&#"3#(K9 =)' 5')9  =(( 5') 3 #/*;Miiol$-)' 93 9)'93 9s# 3.;*q8v `q!3#5!%"&&''/&547'&&''&46675&''77>7&66776>6'%3276654&&#"(K9 =)' 5')9  =(( 5') 3 #/*;ol$-)' 93 9)'93 9s# 3.;*>v"7GQ[_".4>2"26654.5!%"#"'70667#"#'>.&547'654'73MuFFuuFFumllڸl@k:m "]XyC  I]Y AoW@"=T"T="@$FuuFFuulڸllmQk@]$$0$ $0\# # ³ںI?OS60#"'767&#"&'&667&'>7>767>7>767>&>'&&'67667676767676676767>72702327&&'67#"#.#"6#"7"5476''45676''47676''07676%$   )) (-  TV  H-  C@  3AKA/2    s  */W,' $"0  $ !: H->   :'373)-$ e    O-" %4 2% P? %-       - "!"     80. ' 0Lr ")8F^  % 8 +?cmq!!&'6323676'&'6&&'"#"36&'6744&66676#445#"'&&'#44327#3#%323""#0#5""#52623443#73#5##IV6'6=T~S} p 7(@ n "GD&? 2 / ?? * }??>, * > * J??~?>> 6'6U6. !qqON?}8(?"}' IE  3  0 *)?9??>?>}>?O}}>!7%!!!!373#5'!'3533,l{UUUVVUUUUU@ d"kkަjj>>+3:DRZbjx3#66%3#&&'6673#&&%#767&&'#67776733#&''37#667&&'#&&'37673!7673#>7676676733#&'&&%#&'&&'3|b|e|%I$$I%|$I%%I$||c48%I$|I%$$I%I/|/+/|/0|0|"R%$I$I%{$I%%I$|/{/0|0/|0u()| ++ "*|.,{{'(| 2{+" {) $*.;x;>+3:DRZbjx3#66%3#&&'6673#&&%#767&&'#67776733#&''37#667&&'#&&'37673!7673#>7676676733#&'&&%#&'&&'3|b|e|%I$$I%|$I%%I$||c48%I$|I%$$I%I/|/+/|/0|0|"R%$I$I%{$I%%I$|/{/0|0/|0u()| ++ "*|.,{{'(| 2{+" {) $*.;x;)=a7767>7>32.#'.'&#"'67>76672.'&.>>&/.5&*-=R4( >6%%Y 'z 'g%0.<%243J$" @>@J"  EtD5*d2* '!W'c.2 *:./@G:7  %" ?+B6AK3) '!`NL! 0HJ:-/'7L(v< V\bjri -O}#%')66&547045'621760'&'&'&'&'&'&'&'4&'&'45707676'667016670''5&'&'45474454'7>7167&#"#&&'&'&'0'&'&'&'&'&'4&5&&'&'4514501&'67#"'67&'2320&5&4#7016?67650206167670677672610'&'041&&'&'&'&'4'4&'4&&54''6701&'&'&'45&16716716'061010610106106"#"'67663"'63#0#"#2##'676767667>7676636725307"#30323"#30327323"#323"''767676767>7667327327'&'66767567'6767676767676767676701'65167&#"67&'&'054167410#2233'6767"#3236733673'056706567676767676767667467323'676767676#223#223#2273#3#3$#-%^<-[ ; ;.<#&  +9  & " 9s ! %$"#",#           &, %R          4 <0 :, W/  2?    {&&  W V %! *M  #   &G# # 2 @      wSmS +4      8)! &  !   4" )(-"!5"T 0   "*H 5 $  -JG  G#'Hpg zS  2+   ) (!    *vR  "$&),/25:=@EILO%&&'75'3%7672 361" 5017#57%51771'7'7'7'1!'7!1'%--ȆM7 B (C CDiMinڸoXUUUWWkMKUW"**KJ[;@fe1{HrsLHs-"!!QrsQQQQ?Q>%)7!&'2>7%#67#676%7>5#5;$)' M.iS!6YR}$[P &c=!()$w=~,ToVޙ _  '] k& B|Gy]4>'7537#57#5''7'77zz|"B.B//Waaad"ad#d`zz#BBT2aa"ad dd da">R5at#IZ 3Hu %-7#3#3#3#3!3#53#73#53#&&'4546762'6767654&'&"&'&'4546762.'&54767632&'&'&54767632&'&'&#"#&&'&54767632&'&'&54767632&'&'&#""#"&'&4766323"&'&4766323&'&'"#"3276767"#"'&'&547667&'&'&547676732767677#"'&'&547667"#&'&547676723276767#"7"'&&545>7"'&&5456767276654'&'&'77#"'&&'#"'&'&'732767654'&'&'7"7#"'&&'&'&'&'732767454'&'&'7'667232#"'"&&'""#&'&'72326764'&&#"'6767232'667632'67676545&'&#"'6767632'667632'667632'6767654'&'"#"'6767632"&4626"&46312"&463122#"&54662"&4oo22nn11N11nn22oo             ~  ;         %T    $ !'    +U  Ux))  *)@   TT+*     ;             -         > ;  R        T   As    +(eU )1x))  *)A   S*    (k  7         ! g6LLlL#V==+++,#,#f^t^t?U    )1*  *),.    T# +*  ;  ! ;         F  Z           ;           9T   @    +(U  )1))  ))@   T*)      ; [                        ;            UT    T       +' LlLL6#<#bEEbEKV==V=,#,")>o%-LT\ '>Shz%66.767462"462"66.767462"462"&5466321327>'&'&36&'.'&'&7&&'&7632766'7&327654'&#&#"'&4627667677'&'&'&'36676'3667&566.767232327654'&&327667654'&#"&767676'&'6654'&72766'&7l4M^u?Kn4)_B9:3EXVE>PpOOp4J44J4M^u?Ad8-W99:3EXVE>PpOOp4J44J  4/5p3%;:1P3*MEG#/=$ ,C@8 ,)! -6= # (   FR116&*=#&!!   ,1+$? '; &  %!(BQj:=Y+!M64B$1Bl91! DL  " \'+% E.S+ 0F $  )  3L0oppbS8 @Ud[M13?7?054tZpOOpOJ44J30popbS91EQTL=03@6@054sZqOOqOI44I4 \O</Dam<7:% g &.9-(6 4  L4=2%T +  / ILNY Kx15     p6'#H( $(]ZVG34EQK>!/3=3:# -I$")#+#P  !  2,5e|4& .gL. *?$ >vC.''>757'.''#"'>&6779kZ1E^PX''XP^E1 "fe &D.& 4;A;0 &"  ! lƑPEEPk&)58.&3 F><1% >#,5>#"'#"'#"'#"'%#"'#"'#"'> l l  9 v   r r j?2Kbr63223232#""1"#"#"&'4546%632#"&'4546632223232#"#!"&'&54663232 3"#&&546%632"&5546632#"&'&546 4 =*(:) !6 &';,@A*;;*m'9+??*::()9,!5 =U=(+==+0 )"*=5'"6 & 5 5'#7;U;5'#6;*(8;*$7%*==*!4$=V<#7>X9%#654'&#"#0#4#"#336632323266323122" $s.#_99\g>> =6) 6AI6@I]1:B77Csvu+=O_r%"'%&547%624&#!";27>#"'&'463!26654'&7654'&7.33276^  S  S 1##1x H  & , R  33* &f3@z< %Y}('}YT (\& 'D !& .    >4H>Z75353532!>$rq\Z\pr?N 5y%"'#"'&5456767&54766676326#"'&''32773276?667454'&&'&'&#"'&'"#"32767G8(- ?-! %a3J4 +1*#@mL6);3(7E4DD4$02$2Gf<"%+,0H7)   (6 ,%N,- .- 143  $&&(=))*D,, #'4#"$" 5' $&&=?%#"&&547'#"&'"&46327&546327&&5462#"'632\A+H*S#+%>d@Z@@-1hJ554I44$ - Y+8A[.A\*H+0(O'!, -@@Z@&5Jc )$44I4i *"H#[A-v%9'44.'&7767&&77.76676505   ,18 X}A1$\O*!<,#Ş!` a,,4/9tF*(dAFN{@lB s=}Dew 8ERcp} #;N&&'"'&76>766"#"'&547212>7632'&&'"'323>7>7&&'&#"&'&%2&&'"5676#"&&5462"&'&&54546632"32676654&&#"&&'32>546654&"10#"543>54&"12#"'&5462%654&".5466321#"505654&"2#&&'4>:'&47"'&4762'"27650414'&&47"'&4762'"27650414'&4"4"3.#"#0"66"54766#"'.""'&'&#"#"54766#"5&'&#"#"547632'"#&&#"547>"547>#"'&#"&50567632#0'&#"�'"#"&54667AW3=77B'  'D)2r^O-&  %A(Gf.*5LJU1%)#::8"!$:3X8D0),bD*D(" e&!  %<  5&% 8# '!<$  !&>" ' 6' # $9 $+)B0Q)3+-$  2'/!-6Z! 0&JH(1 "2*?G'2<2)3#&' $2BJ̗JqUKG6KJs6GKUo5EIRSHF5I{HJ̗LM\7 G14JJ4u!52GG26MM63Kn4JJ4!8#"/$<#M6 2FF20E  j +    7   +    7  ?,%#.    8  -,  0e    8  -/  +.#.%1> d75373#5'!'3533%23276765454767676763233#&#"3233#"#"'&'&'&545454'&'�"#!&#"#"##5323276765454767&'&'&547454'&#"#53232303GGFGGFGsjGGG+   ' l '   XXYYt||"%&= 9 //! $.2 G 9**''&(**9  G 2.$ !/1 9 <&%>~b74547676763233327676767330212767232#&'&'&'&#"##"#"'&'&'&'##"'#"#">%(<'% ' n&$  $'m ~**$    %**  !   A|#"376545&'&&'&#""5&'&&'&'.''6766775&&'&'.'.'&'&'&'&'&'"&#&'.7>7>73&'&'&'&'.'".'&547>'.'&'&&'&'&'&76763     R 2/     $& ' .4   T$( #/&         ,   %       ( -)yA  &I        #  (!,;# O   ('&)@80 (!/"  4:+ ,/  +.%#  H M  +%  >BWn32"&55#"&55##"#0"#"&'#"&55#"&5546235462366323023235462354>223267.#"#""#"32"&55"&477#"&46332323022102  j^]j   j^]j ]Uz8_8UzzUr  M q XX X\\X XX  XX X\\X XXxU8^7xUUx/n K  ?v6&66&'>5654'676766767654'#"'67&&66767>76.#"7470#&6'&&'&'&&'&&'&547>7>76&547>726553""#"#"&'7733267654&''.5663212632&#"1&#"#"#"&'9"  Z/;P7"H:%  #I Q>.,\U74: yP:2&    GtC  '"$ }4?,! 1 4  2#!2   ,(0#: (# Q3 6K $?% :B25~e'%K~*BPOc(L06MiI<  "  Bm@7    $ YT    "1      &#2    ,&)$?v 6&66&'>5654'676766767654'#"'67&&66767>76.#"7470#&6'&&'&'&&'&&'&547>7>76&547>&'&'"#"0#"'&'&'?327654'&'&'&'&547>7632###5454539"  Z/;P7"H:%  #I Q>.,\U74: yP:2&    GtC  '"$ }    D * *   '   \A\ (# Q3 6K $?% :B25~e'%K~*BPOc(L06MiI<  "  Bm@7    $ YT             !?v6&66&'>5654'676766767654'#"'67&&66767>76.#"7470#&6'&&'&'&&'&&'&547>7>76&547>726553""#"#"&'7733267654&''.5663212632&#"1&#"#"#"&'9"  Z/;P7"H:%  #I Q>.,\U74: yP:2&    GtC  '"$ }4?,! 1 4  2#!2   ,(0#: (# Q3 6K $?% :B25~e'%K~*BPOc(L06MiI<  "  Bm@7    $ YT    "1      &#2    ,&)$?v 6&66&'>5654'676766767654'#"'67&&66767>76.#"7470#&6'&&'&'&&'&&'&547>7>76&547>&'&'"#"0#"'&'&'?327654'&'&'&'&547>7632###5454539"  Z/;P7"H:%  #I Q>.,\U74: yP:2&    GtC  '"$ }    D * *   '   \A\ (# Q3 6K $?% :B25~e'%K~*BPOc(L06MiI<  "  Bm@7    $ YT             !?v6&66&'>5654'676766767654'#"'67&&66767>76.#"7470#&6'&&'&'&&'&&'&547>7>76&547>726553""#"#"&'7733267654&''.5663212632&#"1&#"#"#"&'9"  Z/;P7"H:%  #I Q>.,\U74: yP:2&    GtC  '"$ }4?,! 1 4  2#!2   ,(0#: (# Q3 6K $?% :B25~e'%K~*BPOc(L06MiI<  "  Bm@7    $ YT    "1      &#2    ,&)$?v 6&66&'>5654'676766767654'#"'67&&66767>76.#"7470#&6'&&'&'&&'&&'&547>7>76&547>&'&'"#"0#"'&'&'?327654'&'&'&'&547>7632###5454539"  Z/;P7"H:%  #I Q>.,\U74: yP:2&    GtC  '"$ }    D * *   '   \A\ (# Q3 6K $?% :B25~e'%K~*BPOc(L06MiI<  "  Bm@7    $ YT             !?v6&66&'>5654'676766767654'#"'67&&66767>76.#"7470#&6'&&'&'&&'&&'&547>7>76&547>726553""#"#"&'7733267654&''.5663212632&#"1&#"#"#"&'9"  Z/;P7"H:%  #I Q>.,\U74: yP:2&    GtC  '"$ }4?,! 1 4  2#!2   ,(0#: (# Q3 6K $?% :B25~e'%K~*BPOc(L06MiI<  "  Bm@7    $ YT    "1      &#2    ,&)$?v 6&66&'>5654'676766767654'#"'67&&66767>76.#"7470#&6'&&'&'&&'&&'&547>7>76&547>&'&'"#"0#"'&'&'?327654'&'&'&'&547>7632###5454539"  Z/;P7"H:%  #I Q>.,\U74: yP:2&    GtC  '"$ }    D * *   '   \A\ (# Q3 6K $?% :B25~e'%K~*BPOc(L06MiI<  "  Bm@7    $ YT             !?v6&66&'>5654'676766767654'#"'67&&66767>76.#"7470#&6'&&'&'&&'&&'&547>7>76&547>726553""#"#"&'7733267654&''.5663212632&#"1&#"#"#"&'9"  Z/;P7"H:%  #I Q>.,\U74: yP:2&    GtC  '"$ }4?,! 1 4  2#!2   ,(0#: (# Q3 6K $?% :B25~e'%K~*BPOc(L06MiI<  "  Bm@7    $ YT    "1      &#2    ,&)$?v 6&66&'>5654'676766767654'#"'67&&66767>76.#"7470#&6'&&'&'&&'&&'&547>7>76&547>&'&'"#"0#"'&'&'?327654'&'&'&'&547>7632###5454539"  Z/;P7"H:%  #I Q>.,\U74: yP:2&    GtC  '"$ }    D * *   '   \A\ (# Q3 6K $?% :B25~e'%K~*BPOc(L06MiI<  "  Bm@7    $ YT             !?v6&66&'>5654'676766767654'#"'67&&66767>76.#"7470#&6'&&'&'&&'&&'&547>7>76&547>726553""#"#"&'7733267654&''.5663212632&#"1&#"#"#"&'9"  Z/;P7"H:%  #I Q>.,\U74: yP:2&    GtC  '"$ }4?,! 1 4  2#!2   ,(0#: (# Q3 6K $?% :B25~e'%K~*BPOc(L06MiI<  "  Bm@7    $ YT    "1      &#2    ,&)$?v 6&66&'>5654'676766767654'#"'67&&66767>76.#"7470#&6'&&'&'&&'&&'&547>7>76&547>&'&'"#"0#"'&'&'?327654'&'&'&'&547>7632###5454539"  Z/;P7"H:%  #I Q>.,\U74: yP:2&    GtC  '"$ }    D * *   '   \A\ (# Q3 6K $?% :B25~e'%K~*BPOc(L06MiI<  "  Bm@7    $ YT             !?v6&66&'>5654'676766767654'#"'67&&66767>76.#"7470#&6'&&'&'&&'&&'&547>7>76&547>726553""#"#"&'7733267654&''.5663212632&#"1&#"#"#"&'9"  Z/;P7"H:%  #I Q>.,\U74: yP:2&    GtC  '"$ }4?,! 1 4  2#!2   ,(0#: (# Q3 6K $?% :B25~e'%K~*BPOc(L06MiI<  "  Bm@7    $ YT    "1      &#2    ,&)$?v 6&66&'>5654'676766767654'#"'67&&66767>76.#"7470#&6'&&'&'&&'&&'&547>7>76&547>&'&'"#"0#"'&'&'?327654'&'&'&'&547>7632###5454539"  Z/;P7"H:%  #I Q>.,\U74: yP:2&    GtC  '"$ }    D * *   '   \A\ (# Q3 6K $?% :B25~e'%K~*BPOc(L06MiI<  "  Bm@7    $ YT             !?v2&66&'6665654'67676676767654'#"'67&&66767>76&&#""747"#&6'&&'&'&&'.'&547>76676&547>B "   Q/; O6-^>  #I Q>.Wq=U859  yP:2&    f  'F} ("  Q3 5I K1 :B26}e'%K~*BP4P0(L NiI<  "  e7    @YT    ?v6&66&'>5654'676766767654'#"'67&&66767>76.#"7470#&6'&&'&'&&'&&'&547>7>76&547>726553""#"#"&'7733267654&''.5663212632&#"1&#"#"#"&'9"  Z/;P7"H:%  #I Q>.,\U74: yP:2&    GtC  '"$ }4?,! 1 4  2#!2   ,(0#: (# Q3 6K $?% :B25~e'%K~*BPOc(L06MiI<  "  Bm@7    $ YT    "1      &#2    ,&)$?v 6&66&'>5654'676766767654'#"'67&&66767>76.#"7470#&6'&&'&'&&'&&'&547>7>76&547>&'&'"#"0#"'&'&'?327654'&'&'&'&547>7632###5454539"  Z/;P7"H:%  #I Q>.,\U74: yP:2&    GtC  '"$ }    D * *   '   \A\ (# Q3 6K $?% :B25~e'%K~*BPOc(L06MiI<  "  Bm@7    $ YT             !?v6&66&'>5654'676766767654'#"'67&&66767>76.#"7470#&6'&&'&'&&'&&'&547>7>76&547>726553""#"#"&'7733267654&''.5663212632&#"1&#"#"#"&'9"  Z/;P7"H:%  #I Q>.,\U74: yP:2&    GtC  '"$ }4?,! 1 4  2#!2   ,(0#: (# Q3 6K $?% :B25~e'%K~*BPOc(L06MiI<  "  Bm@7    $ YT    "1      &#2    ,&)$?v 6&66&'>5654'676766767654'#"'67&&66767>76.#"7470#&6'&&'&'&&'&&'&547>7>76&547>&'&'"#"0#"'&'&'?327654'&'&'&'&547>7632###5454539"  Z/;P7"H:%  #I Q>.,\U74: yP:2&    GtC  '"$ }    D * *   '   \A\ (# Q3 6K $?% :B25~e'%K~*BPOc(L06MiI<  "  Bm@7    $ YT             !?v6&66&'>5654'676766767654'#"'67&&66767>76.#"7470#&6'&&'&'&&'&&'&547>7>76&547>726553""#"#"&'7733267654&''.5663212632&#"1&#"#"#"&'9"  Z/;P7"H:%  #I Q>.,\U74: yP:2&    GtC  '"$ }4?,! 1 4  2#!2   ,(0#: (# Q3 6K $?% :B25~e'%K~*BPOc(L06MiI<  "  Bm@7    $ YT    "1      &#2    ,&)$?v 6&66&'>5654'676766767654'#"'67&&66767>76.#"7470#&6'&&'&'&&'&&'&547>7>76&547>&'&'"#"0#"'&'&'?327654'&'&'&'&547>7632###5454539"  Z/;P7"H:%  #I Q>.,\U74: yP:2&    GtC  '"$ }    D * *   '   \A\ (# Q3 6K $?% :B25~e'%K~*BPOc(L06MiI<  "  Bm@7    $ YT             !?v6&66&'>5654'676766767654'#"'67&&66767>76.#"7470#&6'&&'&'&&'&&'&547>7>76&547>726553""#"#"&'7733267654&''.5663212632&#"1&#"#"#"&'9"  Z/;P7"H:%  #I Q>.,\U74: yP:2&    GtC  '"$ }4?,! 1 4  2#!2   ,(0#: (# Q3 6K $?% :B25~e'%K~*BPOc(L06MiI<  "  Bm@7    $ YT    "1      &#2    ,&)$?v 6&66&'>5654'676766767654'#"'67&&66767>76.#"7470#&6'&&'&'&&'&&'&547>7>76&547>&'&'"#"0#"'&'&'?327654'&'&'&'&547>7632###5454539"  Z/;P7"H:%  #I Q>.,\U74: yP:2&    GtC  '"$ }    D * *   '   \A\ (# Q3 6K $?% :B25~e'%K~*BPOc(L06MiI<  "  Bm@7    $ YT             !>w(3Siu#&57#&7'&47'&5&'0575436760321752767633#0541527743422000"'&''74223327022'7320'&'32#'676773##'7101""1&#"0"14#76762""1&#"'&&%0#0"#'&5&''&5110#0#4776333272612%2131##0"#'&SWrX c F S )  6 t/]x;LKKg C*  h  ^ nX}g  lS$ X Yyd  (.9@D3SA/' <&G L-kf /VW8 54vw. >vVe'&'&'&#"32767654'422#"'&'&&'&545454767667262164'&'&"T(2E3zgXWFFJS/-v>6`</,vla-!iM('^_&&|iVL` &op*) Uv'.5=E%%3735#7#33!53'377#3''337?'#15''#1''5UZEb3lIIWWw9[##l yD@cr6cluy5P ǂMMv+!t0 02}2L'AA2^  b  Vv>HS[d%%3735#7#337'&''4''&'#1"332?6554%''&47712''76''#'&'#VYDb3lHHWWw9Z6,  q@76 -t17,_tty64uv 6PǂMM6!,Ao 7 6/WtU7,0sBG5 uu36 Uv'.9A%%3735#7#33!53'#''3#''3373?#'31?UZEb3lIIWWw9[#~"k xC?b5;5b  btP ǂMMz+!p0 /1|2K&n0>2~ `  PWNe1%%153733'#73326654&''&&546323232.##"#"#"#"&&''326553#"'"#"&'>>>_33_K & ++8'28  1.B4/%   G9(!6 Pŀγ   1)'7$   1*/4! (9$Uv-o%%3735#7#33726553#"'"#"&'77353267654&''.54>30323232�#"#"##"&'UZEb3lIIWWw9[4  ?3$1 4     !1  "0#:P ǂMM  $3     &      &($Uv'aB~3%%3735#7#33#!"&55&5467!20#"#"'47672327654''&54547672&'0#"0#"#"'47672327654''&54547632&'0#"0#13232767#"#"'&'&'&54504547676767035054'&#""1"65<547677'&'&74454'&'53##7445477675&'&754'&'#327670##56765455454767&'&5455454'&'531%&767673##&'&754'&'56765'335&754'&'676'4&5476735#73##"'&54=4'&'567654554456767"#"'&5475323UZEb3lIIWWw9[#""#g $   $  ML      P&\      C                               # 2        i  P ǂMM"""#[                           %  - %  - !     !m 3  (   &  , x   +  3= .    % !*   !  %  (  2  ,      U:#&.9%%3735#7#337>.3232'47676&'&#"#"#&767&'&'01"#"'&547676677&'&'#"&77#0#"'&54567#"''&547677.'&>7676#"'&'5632766'&&776725632677667>56323267676327263654'#67367&2>'65"7'6564'UZEb3lIIWWw9[!yd_Exd_&          - /9%7F7b 0'! ;H U>i0 #   z   \ L P ǂMMYcXd"          &  *'& 0#!GJ45 $     &  !   w    W *   Uvn~%%3735#7#33&2"&4%&&327654'632767&&#""#"#"'.5476#"'&327>#"#&6767UZEb3lIIWWw9[vV0=03:  (   !*\6L @3 $  P ǂMM$+( ""  !   .0B  "+    QXap1%%153733'#7&'&'&0#"'&'&'672677327654'&'&'&'&547>7632###54547033>>>_33_J 2 #D  +   )' ]A]}|Pŀγ            Uu]k%%3735#7#337&'&'&#"#"'&'&'?327654'&&'&'&'&547>7632###5&5473UZEb3lIIWWw9[_   D *  *    ' \A\P ǂMMt           3J 7z7%'%%#'33''''#5726553#"'"#"&'77353267654&''.54>303123232�#"#"##"&'{HHB=qqx8ifeTS_^%d=4  ?3$1 4     !1  "0#:S2`쿽45qnS  $3     &      &($3J gu7%'%%#'33''''#5&'&'&#"#"'&'&'?327654'&&'&'&'&547>7632###5&5473{HHB=qqx8ifeTS_^%d=a   D *  *    ' \A\S2`쿽45qnSt           Vv>HS[d%%3735#7#337'&''4''&'#1"332?6554%''&47712''76''#'&'#VYDb3lHHWWw9Z6,  q@76 -t17,_tty64uv 6PǂMM6!,Ao 7 6/WtU7,0sBG5 uu36PWNe1%%153733'#73326654&''&&546323232.##"#"#"#"&&''326553#"'"#"&'>>>_33_K & ++8'28  1.B4/%   G9(!6 Pŀγ   1)'7$   1*/4! (9$Uv-o%%3735#7#33726553#"'"#"&'77353267654&''.54>30323232�#"#"##"&'UZEb3lIIWWw9[4  ?3$1 4     !1  "0#:P ǂMM  $3     &      &($QXap1%%153733'#7&'&'&0#"'&'&'672677327654'&'&'&'&547>7632###54547033>>>_33_J 2 #D  +   )' ]A]}|Pŀγ            Uu]k%%3735#7#337&'&'&#"#"'&'&'?327654'&&'&'&'&547>7632###5&5473UZEb3lIIWWw9[_   D *  *    ' \A\P ǂMMt           Vv>HS[d%%3735#7#337'&''4''&'#1"332?6554%''&47712''76''#'&'#VYDb3lHHWWw9Z6,  q@76 -t17,_tty64uv 6PǂMM6!,Ao 7 6/WtU7,0sBG5 uu36Uv-o%%3735#7#33726553#"'"#"&'77353267654&''.54>30323232�#"#"##"&'UZEb3lIIWWw9[4  ?3$1 4     !1  "0#:P ǂMM  $3     &      &($Uu]k%%3735#7#337&'&'&#"#"'&'&'?327654'&&'&'&'&547>7632###5&5473UZEb3lIIWWw9[_   D *  *    ' \A\P ǂMMt           Vv>HS[d%%3735#7#337'&''4''&'#1"332?6554%''&47712''76''#'&'#VYDb3lHHWWw9Z6,  q@76 -t17,_tty64uv 6PǂMM6!,Ao 7 6/WtU7,0sBG5 uu36Uv-p%%3735#7#33726553#"'"#"&'77353267654&''.54>303123232�#"#"##"&'UZEb3lIIWWw9[4  ?3$1 4     !1  "0#:P ǂMM  $3     &      &($Uu]k%%3735#7#337&'&'&#"#"'&'&'?327654'&&'&'&'&547>7632###5&5473UZEb3lIIWWw9[_   D *  *    ' \A\P ǂMMt           uws#K3#"'&'&545414767670327655327>745&&"&&'&#"     pq$  '*+ baJJ hhhh ab  2"Vv>HS[d%%3735#7#337'&''4''&'#1"332?6554%''&47712''76''#'&'#VYDb3lHHWWw9Z6,  q@76 -t17,_tty64uv 6PǂMM6!,Ao 7 6/WtU7,0sBG5 uu36PWNe1%%153733'#73326654&''&&546323232.##"#"#"#"&&''326553#"'"#"&'>>>_33_K & ++8'28  1.B4/%   G9(!6 Pŀγ   1)'7$   1*/4! (9$Uv-o%%3735#7#33726553#"'"#"&'77353267654&''.54>30323232�#"#"##"&'UZEb3lIIWWw9[4  ?3$1 4     !1  "0#:P ǂMM  $3     &      &($QXap1%%153733'#7&'&'&0#"'&'&'672677327654'&'&'&'&547>7632###54547033>>>_33_J 2 #D  +   )' ]A]}|Pŀγ            Uu]k%%3735#7#337&'&'&#"#"'&'&'?327654'&&'&'&'&547>7632###5&5473UZEb3lIIWWw9[_   D *  *    ' \A\P ǂMMt           Vv>HS[d%%3735#7#337'&''4''&'#1"332?6554%''&47712''76''#'&'#VYDb3lHHWWw9Z6,  q@76 -t17,_tty64uv 6PǂMM6!,Ao 7 6/WtU7,0sBG5 uu36PWNe1%%153733'#73326654&''&&546323232.##"#"#"#"&&''326553#"'"#"&'>>>_33_K & ++8'28  1.B4/%   G9(!6 Pŀγ   1)'7$   1*/4! (9$Uv-o%%3735#7#33726553#"'"#"&'77353267654&''.54>30323232�#"#"##"&'UZEb3lIIWWw9[4  ?3$1 4     !1  "0#:P ǂMM  $3     &      &($QXap1%%153733'#7&'&'&0#"'&'&'672677327654'&'&'&'&547>7632###54547033>>>_33_J 2 #D  +   )' ]A]}|Pŀγ            Uu]k%%3735#7#337&'&'&#"#"'&'&'?327654'&&'&'&'&547>7632###5&5473UZEb3lIIWWw9[_   D *  *    ' \A\P ǂMMt           Vv>HS[d%%3735#7#337'&''4''&'#1"332?6554%''&47712''76''#'&'#VYDb3lHHWWw9Z6,  q@76 -t17,_tty64uv 6PǂMM6!,Ao 7 6/WtU7,0sBG5 uu36PWOf%%11%%53733'#73326654&''&&546323232.##"#"#"#"&&''326553#"'"#"&'Y>ih>ZY>i_33_K & ++8'28  1.B4/%   G9(!6 bE ŀγ   1)'7$   1*/4! (9$Uv-p%%3735#7#33726553#"'"#"&'77353267654&''.54>303123232�#"#"##"&'UZEb3lIIWWw9[4  ?3$1 4     !1  "0#:P ǂMM  $3     &      &($QXbq%%11%%53733'#7&'&'&0#"'&'&'672677327654'&'&'&'&547>7632###54547033Z>hi>YZ>h_33_J 2 #D  +   )' ]A]}|bE ŀγ            Uu]k%%3735#7#337&'&'&#"#"'&'&'?327654'&&'&'&'&547>7632###5&5473UZEb3lIIWWw9[_   D *  *    ' \A\P ǂMMt           Vv>HS[d%%3735#7#337'&''4''&'#1"332?6554%''&47712''76''#'&'#VYDb3lHHWWw9Z6,  q@76 -t17,_tty64uv 6PǂMM6!,Ao 7 6/WtU7,0sBG5 uu36PWNe1%%153733'#73326654&''&&546323232.##"#"#"#"&&''326553#"'"#"&'>>>_33_K & ++8'28  1.B4/%   G9(!6 Pŀγ   1)'7$   1*/4! (9$Uv-o%%3735#7#33726553#"'"#"&'77353267654&''.54>30323232�#"#"##"&'UZEb3lIIWWw9[4  ?3$1 4     !1  "0#:P ǂMM  $3     &      &($QXap1%%153733'#7&'&'&0#"'&'&'672677327654'&'&'&'&547>7632###54547033>>>_33_J 2 #D  +   )' ]A]}|Pŀγ            Uu]k%%3735#7#337&'&'&#"#"'&'&'?327654'&&'&'&'&547>7632###5&5473UZEb3lIIWWw9[_   D *  *    ' \A\P ǂMMt           Vv>HS[d%%3735#7#337'&''4''&'#1"332?6554%''&47712''76''#'&'#VYDb3lHHWWw9Z6,  q@76 -t17,_tty64uv 6PǂMM6!,Ao 7 6/WtU7,0sBG5 uu36PWNe1%%153733'#73326654&''&&546323232.##"#"#"#"&&''326553#"'"#"&'>>>_33_K & ++8'28  1.B4/%   G9(!6 Pŀγ   1)'7$   1*/4! (9$Jv-j%%3735#7#337326553#"'"#"&'77372654&''.54>3032632&##"#"##"&'JZDb3lIIWWv9\4 @3$1 4    1  -(/#:P ƂMM $3    &     +&)$QXap1%%153733'#7&'&'&0#"'&'&'672677327654'&'&'&'&547>7632###54547033>>>_33_J 2 #D  +   )' ]A]}|Pŀγ            Jv\k%%3735#7#3370&&'&'&0#"'&'&'67677327654'&'&'&'&547667632###5454763JZDb3mIIVVv9[_ 2 "D * *    *&( [B\{|P ƂMMt          %  ]W9[1%%153733'#7>32323267#"#".'&#">32323267#"#".'&#">>>_43_J)@$#8#  -S7"8#  .&3#7$! . R7#7#  .Pŀγ#: 6G."5H> 9H71%%%%76767&''&''&#"''3266?'''>32>JKN   w    ; *-* 0 a5˔M\r B "  5N=  M.0#  ->X-?212767..'2677632376767&&' JH>""  $ʺ#  &*"DG9R.01-U:1$82X-) 3 1:96 )7 GK,WY,MtRFGKCr)8Rb4>2"&&763201##"'"'&'&''440720#'&'"4"#"##"5436763220'"516767630&'&547667667>366.'&'.#766262>77>4&'1'&'.'&&76672"'&#""5436767676763233267321#"&#"#0212232764WyyW4WXj " ** L')  # )(L )+  2?"O#,5'7#'737!'#''773'#'7'!73'7333'2:r:s0s92Qtb8:0Q:yb9:0Q:)V0r92PuB2:r:uZj-.k\j-jj/loujj.l [j-hZj-/l D7 5AXk'77'7'"#".''7&&5441463232'335127&''"#"&''73365053'32653#"#"&'%3052654&''&&463232&�#"#"#"#"&'x34oocoo91'w$+bK!<.g5=h6Y^ XF=)C~ /!$46G:%>E=8&A+0P&I~~dccLLdc*&EJ,F[*$BL?K\  :0$W' XR_,BC-$3 vT2".466527%654'%&"3232654##"''&547%62"''&#"#2654&'.5432332505&&#"#"&'&##"uFFuuFFu@""$""T!&-2 ( )$W@AR kLTBO3-R2+* NMDNFH24 -3<-* "GFuuFFuu 'r'''15/n 3r1 ? 942)  -! 97711* " &$twtU27%654'%&"5"''&=4''&"0"'&55477&54777673763  W  W | 44 |  *^]7/    t GGB 09&55W90pvxDw"''&76723227%654'%&"654332#"#"''&547%62#433232654&&'&&546321##"'&&#"#".k RAW$) ( 2-&!T""@$@""= )-<3- 42HFNDMN *,1R-3OBTL2G" ? 1r3n /51'r''' x " )20779 !-  )159$&>%!!#5#735335#335353#735335335>lJ1a11abb11a110100111aÒ>v !$'*WZ]`cfiloruy|!7'7!''7!7'!77''7!'!"'#''#5"#&5'&554767!23763032%'77!!'7''7''77''777!!'YתS2Rs4ߩǩSSĩǩԩ2wR+dª_󪩻2& 5 r֪>窪S)ǩש2xv,2Jcm %!"1".'66321!232#"!0#"&'66321!232#"0#"321!212654&#"1!"1"&'66321!232#"&"26514!"1"&'66321!232#"1"321!212767&&#"#"&5466326&2645!!!!!#33#5335#%!5!!5!%!5!!5!%!5!!5!366545'4ᘖ&'0#"02"'5&5462I IIEJ  4J I   B    ccccebcebceb1% $1 11y1/  / w  /    HK0020;0;010/:20.;20.220.__n%6  .)RS'' wa%How~#"'&'&'&=0547676207#"'&'&'&=454767626545&7454'&'&"101327676"&462''7@#6KMTl4  5CZELu9 e"  1g~QIGΒW9H9>[<1H27)?b-A :;;: Ov*:I!&&5041454667!70414&'"32>02654&�%3264&#01Vy8_8Vyz&V<=VV=6'K&67M7+,,,,zW8`9{VVz=VVzV'56&&77&++,>-, ^v /'7'%'7'77575757&'&''5ԘfD͑MU fJ0RKQlMPKOFMjHJFVVV[UUIJI=37& >v,;W!!33#&'#53323276765045%3<53#&'#3#"##"'&'&54756673>lqm('mn'(NN   wl('nm'(mm  IOyss  'Vic  ssBr7SV327!#"'&54767'3%#"##023277654'35767#"#!"#"'&5476773!M8 (W _J" rh  % (   g5  T44 R)>v"75AQ_iu%)7ERXhmty!&&6367667>%07#"#'6767666020"""#4'!054766767'&47754&'4545235:3235354&'45235&'76674665232234735#67654536123676767567&6'67333#3345&5456354'767>70323767"'6754&5&5452753445&''6323454'&33445&335&#"3&6'&'35&170354'&73445&#"7354''632#447470#45473445&'35054#"#7346'&#"34454'0#"7013654'&76&#"<6#344'!35#64'#!5#3#%3445&6'0345&&#0345&'&#"'75#345&'"#"735#7&6&'3'30414'&013445&#33654'4#"#7354'&#735&'10346'&356323'&>lD      A    9*             1   <s ! t % # &) ! $  l   ,  &3 - %vd P B  bb 2J B$   + H X*8  & (%11 A ) E> - c                 +*)** g#,+)       7    @v3?KS[%#"#"#"'&545454766323232�&&#"3232327'533##5#5!533##5#53#3%#53#53aEL pMJS(j<A>8DH11./EH=[9::9::99:9eeee QVq |]*/#59KH83&99:99:99:99:-kE FEF@v8@H3276677#"'&&'&547667632&'&#"3#3%#53#53a E)"/:>W%'OI9TU ?;@[o=H1M ߹eeee-E>!( 7# U@> Z;LQSLPkfC [;.!FlF FEF>%454'&&'&'&&54454'&'76322233276767667667632367"''66&&'&'&#"#&#"##"'&&'&&''&'&&'&&'&'.#'&'&'&'&#"#"66766767676763276767>767>7735&&'>767>767>72&1&'454'&&'&'4&505&4'&5743223276767667>7632267&&16&&'&'&#"&&#"##"'&'.'&'&'.'&'.#'&'&'&'&#""5463##"'&'5>567667676762 "! (  6                      n'.0      B)*) \ ##     6             A-H /+     &%        $       +  (+   )   (, )y  )- %$          .@e  +  >-Pgov362##"#&##&$'$3#3#3#3#3#3#!276'&'&523276'&76'&<4Ri(   +>ZX$!"#-c="T  'O ().0+,1212%%  P<JhE(2'2(2'2(2'P@]'!+IOPPFVPPQK >V  '07>HQYa#454''7&'7&'7632'67'67'67'67#&547%654&#""&462&"&462f )j[@"'KFn#4:7k*$6AKM9U^ /W[ S<((!!x\fHA~0Q6N   t f4V$(5O=+-NL(($!!?v 877&32#"&'?532664&&"&54>3lV22yXuEEuXb= 4RdbbǨb!EuYqV22 FuuFQG =EbƩbbc(' BFYuEh`~\x|5HIu{ %*049>DHOTY]bflty~H)3@JYntx|5@"#"''#"''&#"#"'&'&'&767676767676767663727#7'77&'6765477677&5477'&547677&54767&547676504147##'2#67632#"'&%4'#077326327654'67676'&#"&&'4'&'456'654'6'656567445654#"0&54'&#"&'&#"3276323254'&'&'.67673277'&'#"''&&#".'&'&'&''5&54766767>77676476765476067&67&667&>7&66'46676#22326%#70547&4767632>5463213&'&'"&'&74#"&'.#"327632654.'&547#'&&#"#".''77&%&'263232654'>76'&#"&&'66#"'&#"#"'#274777&7676>505476322326'&'#"''&&#".'"'66323764#'"2132745#"'.'"&'327&'&2632767654'45&3"'&#236'>3&&#630212367#"%6#"#&1'&5&54#6'&4632'6326&2'4'&'&5#'&&'4&''&''0&1&'465.''&''67#&.''&454&'&"14'"'3'4&1"&&#'&''0&14&563&"#6'"'3'0&"''&''&&''054&.''&654&5&&''&''&546'&''46147#&0&''4654&1#&&&''&766767676775&'&6'&'&'&'&'&'"#"'&'#"'&'&'&'01&'&'&'&'&''5&54766767>7"1327254'65#'#"5&547&547&54767&54667&5476'476763201654&56'0#>7654#"04'34'65#&67&67&667&>7&66'46676'2#&'767"#""&7&7"&327.327"&3270#.'"763274'&54&'&5'&'4'#6&''0&10&'465&''&'4&54&5607'677&'&'&'6'0'&76327&&''"'&&'&&6'#"1&7327&47327&47&54727&47&'6323&7327&7677&5476! ) 3W@ \{1.&!_8/B."%$'       ! %*/6 ! D ! & 0.'%          ""#(3F! ;g$(,.@s+ B[. RD- eB+vB0-%B0* p+$"         $),, J<J          .#4$'-/,SG-N,.0+M-+wB0-'#&B&.   (  2/".3 &   &-.    $),,  RC- eB+vB0-;W:# '&            S      g  '          ?9    "C  ' %      Cn+$#$ $##,p ?     )1'*. #%" p+$"  &            ;       `       !*'     J     VU5R 50  &;6'5H!(! H9HEM        :$ )22iR \* 9=A>,                                                /CG;;-_ 3-  m5 ! &  $ (!%" :{8.4  !6:9:+!w ' "13%(" + ":. ,*%&   !69=u 4J              #    .. "$         #          1         ? ' $, )?I %27) EU,:'&=  #    Cv 48Z!""#'27677#"#!32#"'&54545476'%%%654'654'&#"#""#"3230232a& <6 ~BAU! 24! 34( &@&T+,8"!"!ck.97-)+/97,*S+2'6654&"&&546"'.5462pq_^4>>4^_qp-#52`25 IhIpqr7j>__>j7rq'@  %4II> 3?C623!"'66%3"##&&'3#663!33276453227!3##"'&45!uiw F(aE%#$$$% F''1/&pp N +5?e%%5%%%3717''''&55466254''&54''&2$$7654'&'&'&'&'&$#"32$76767676767676767654&$$#"$7"332$$$$#>JJIKKWYY'r^ll*m&<>)'G%,2^Hh@Фw?Qhm/o[V7YdWq* VE+#2!GSZbhntz'1;CLT[biouy!(/5<BJPV\`dhsz &*8?FMSY_gtz&,4;HNY`gntx~   $ 0 ; G P ` l v    & 6 ? J V ` j s }    ( 2 > H R ^ j v    ( 5 A M \ j u  )9GWgw -=M[iy ,<JZhw$4BRbr%5EUes )9IWgw$5CQaq ,;IYiy#1<GUcs(6EScv/=KYiy %.>LZhx /2"464646&4664&#"22&52&546'&46'&24&"24674&"266'"&632174#"265442424642"&63252654'&4674&&#"326&4646424224&"2"&6325'32654'&4664&"2&464664&"2'2&54464674&"266464646&4664&#"2"&6325'2654'&'"&63252654'&4264&"24674&&#"26"763274&&#"32>"&63264&&#"26'"&632"'&2654'&"&632264'&'46&4&#"242'"&6322654''&"&63612654'&"&6321252654'&"&636126654'&&4664&"246&464646464664&"32&4664ᱭ6'732654'76'732654'>257654'>264򱩐&32714&"3&&#"54&&#"31"&#"&&54&#"3&&#"&&'4&&'&52654&&#"&'6&&'6654&67776576'732654'732654'7246&2&52&5462&54646&4664&#"246&2&52&542&52&52&52"&'"43222654'&"43202654'&"&6322'2654'&4&4&#"24&2&52'&5'6#"462"&2""&63252654'&46&&464674&&#"326&&462"&'&544664&"22"&4674&"2664&'4&46&2&5462'&'42&5&422&5&2&52&5462&54646'&&4646464244224&"32'46&2&5&464674&"32646&46"&6321252654'&4674&"26&4664&#"246&4664&"322&52&5#"&64646'46&4&"324274&"32>2&52&52&54646&464&4&#"324664&"324674&&#"262"&46"&632026654'&&4674&&#"326"767526654''&&4674.#"2646&4&"324664&"2"&632226654'&&4664&"24674&&"326&4664&"3246&&2&52&5464246&464674&#"32>"&6320'32654'&"&6320'32>4'&"&632"&63232774306376&#"&6321772&1432177654'4#"&76'&'&&#"#"1#"'&#"132&50614&"'&'&#32#"54#"'&#"#"#"&07632"6'&6236776&#&5467765430572&546776&'"546772&546776&"32654&"2654&"2654&'4&&#"264&"264&"26'4&#"264&#"264&#"264.#"26'4&&#"264&#"32664&"26'4.#"264&#"32664&#"264&#"32664&"32674&#"26'4&#"32>4&#"264&"264&#"32664&#"326'4&"264&#"32664&"264&"264&"2664&#"26'4&#"264&#"264&#"32>4&"264&&#"264&#"26'4&#"264&#"264&"26'4&#"324&&#"326"2654."2654&"2654&&"32654"2654&"2654&"2654&4&#"26'4&#"264&#"266'4&#"264&#"264&&#"264&&#"26'4&#"264&#"3266'4&"264&"264&"3264&"3264&"32>4&#"26'4&#"264&&#"264&"32664&#"264&&#"26'4&#"264&"26'4&#"32664.#"26"32654&'4&"3264&"262654'&4&"26264'&'4&"32>4&"32664&#"264&#"3264&#"3266'4&&#"26"2654.74&#"264.#"32664&#"264&"26264'&'4&#"32666'&"13215&#"454'&#"321"#"454'&#"30305&#"4'&#"2&#""#"4'&#"367654'&#"&'&'4325654'&"&'324#"1454773276545701327654'7327654'򱫍327654'732764'7327654'&#"172327657327654'72327656&#"3254'&#"3254'&#"3254'&#"327654'&#"327654#"'&547632'&#"327654'&#"327654'&#"327654'&#"327654&#"327654'&#"327654&#"3254'&#"327654&#"327654'&#"327654'&#"327654'&#"327654#"'&547632'&#"327654&#"327654'&#"327654&#"327654'&#"327654'&#"327654'&#"327654'&#"327654&#"327654'#"'&547632'&#"327654'&#"327654'&#"327654'&#"254'&#"327654&#"327654'&#"327654'&#"327654'&#"327654'&#"327654'&#"327654&#"3254'&#"327654&#"327654'5&#"327654'&#"327654'&#"327654'&#"327654'&#"327654#"'&547632'&#"327654&#"254'"547632'&#"254'&#"327654'&#"327654'3&#"327654#"'&547632'#"547632'&#"327654'#"'&547632'&#"327654'&#"327654&#"327654'&#"254&#"327654'&#"3254'&#"3254'&#"3254&#"3254'&#"327654&#"327654'&#"327654&#"327654'&#"327654'&#"327654'#"'&547632'&#"327654&#"327654'&#"327654&#"3254'&#"327654'&#"327654'&#"327654'&#"327654'#"547632&#"327654'&#"327654'&#"3254'&#"3254'&#"3254'&#"327654'&#"327654&#"3254'&#"327654'&#"327654'&#"327654'&#"3254'&#"3254&#"327654'&#"327654#"'&547632'&#"327654'&#"327654'&#"327654'5&#"327654'&#"3276543&#"327654'&#"3254&#"327654'#"'&547632'&#"327654'&#"327654'&#"327654&#"327654'&#"327654&#"327654'&#"327654'5&#"327654'&#"327654&#"254'&#"327654&#"327654'#&#"327654#&#"327654'&#"327654'&#"327654'&#"327654'&#"327654&#"3254'&#"327654&#"3254'&#"327654'&#"327654'&#"327654'&#"327654'&#"327654&#"327654'&#"327654&#"327654'&#"3254'&#"254'&#"254'&#"327654'&#"327654&#"327654'&#"327654'&#"327654'&#"327654'&#"254'5&#"254'&#"327654&#"327654'&#"327654'&#"327654'&#"327654'&#"327654'&#"327654&#"3254'&#"3254'&#"3254'&#"327654&&''&&776#"4321"#"'&547'#"547632'#"'&547632'#"'&'73032#"547632'#"'&547632'#"'&547632'#"'&547632'#"'&547632&1&547632'#"#5#"'&547632'#"'&547632'#"'&547632'#"'&547632'#"'&547632#"'&547632'#"'&547632'3#"'&547632'#"'&547632'#"'&547632'#"'&54572#"'&547632'#"'&547632'3#"'&547632'5#"'&547632'#"'&547632'5#"'"054547632'#"'&547632'#"'&547632'#"'&547632'#"'&547632'#"'&547632&547632'#"'&547632'#"'&547632'#"'&547632'#"'&547632'#"'&547632#"'0"1&547632'3#"'&547632'#"'&547632'7&&'6327&'&&77676&'&'m  l                  #             u #     s  l { p ) )  h   xl   7 ( (  0 <     3      Q  /  Y =%     L       #   | )  #    " "{ )    h  sin  f x g           #         &           j j zj        h   } }  ~l    ))]K > M_P]v  )C zV X  C     ,    J    9  O f   9        d              3 d   4 $ i h    f g             d   9      5   0    e      k+/K]XVzB(   v]P_MH5   !     h g c   ! f ff!     }      ~                  ~}!      cc    d ee   } ~                 ~~     !  cc   ff g      } ~      }~            }     c   !!   ,  g i ~o   kg     i   g&,N%,             "                    2  " "  % )"    *    D  '   K 3     @                {       #                     $$N?#*@PDNc   9 gH I d  `   y   J )  .    *        L       -  -"            ) ;  w H Z    4?NJGf8 dOCP@ *H    }  ~  ~ ~                 ~ ~   ~ ~~           }~} } ~                 4  3  .    r            4"'4ì4"'&?$41hz632632#!"'&'&54767&5476763267632632!&'&5416767&'454763236327654&'&#",(&/*+/ 0)/?2& (? &A<,< 7$#&1:(& +< d#%  0"*#/1'.% &!: 5G(   .  ),)+1 *+  $ & $ e!)5!$462#"5!$462#"5!$462"c0D1&"0E0&#0E00E@I33$(AH33$(AH33H3 5!5!53N,Zz#&'&'&'&505454767676731#567676765445454'&'&'&'53%"1"'&545454632121232#"0#"'&54545463221232#"%#5##"1"#"'&5454774#"56763253230327654L  L   L  L &&k ;$/ ~cAF6"%&jC7 :$&()-.3423,+)(&% %(./6645/.'%T33.,*(&$ %'.04565/.(% %'()+,33E&& < '#&0|E-c Q 1"Gv 2Baz1IT!!''!!'#!5!!5!%+356767676545&'&'&'"##53212#"#7"##3232767676541454'&'&'&'&##52320#"#7#3535#5356'&'77'432&55&767#%44."#"&'67&&"667676667772>&7#7#%2###5330327654'&#"1#3721230#"##5332103276767654554'&'&#"#37#3##53v$tSs]!  s     L )),c&"e = 9! x  "#% .'( &5=)  !r     ,)) LߵO/ձ3  G. H  {` z<"" C&!%'%#& %r %./2 &$  ?&%)  Lk  2H .H  {`{#;hy )59!!7!!&5&'&#"654&654'&#""'6327&'!5!677&7632;21454'&'677632326'&#"767&&'''&5476776767&'&'5!!5!!!%3232#"###7303276541454'&#"1#323276545454'�##'32320#"##533#3##5ka`_fcb>Z%,M?3;V< b C6!      .'($@  -9\X+& A%/[ LP *t 0{'   \    i@)&&a _n5bb@ +M 3-@O > c 6!? p),)4=<-   /8"'1  " $20,%   *b!J    y3y>vSu".4>2"&&'.'".5&'&&>767&'&3601654'&'&'.76767&76.'&'6767654'4'&746.'&'&'45476767654'">4'32.434"25FuuFFuuF=5J     :5- 9/"$  )) "'-26  %*# (G7-;9L'  1$0#7>.   J !,YuFFuuFFuY [C; !B:D7#AC$#   ,$4^/*5% * &19C-)$+:(q*%518HSC"1  BKNB/8ED;6 *"#-@4E[D9 ,4G?3F?&;P ;    2\F^)?G]t&2Rrz#3Co 2IUgu0CQ_ly=]m}%#"'&5467632"#&&547>32"232767654&'&&&6"'&'&5467232"&#"276654'&'&#"'.547632"'&&54767632""32327654'&'&"#"'&'462#0"5"32654&&"&462'"2654.&&66"'&&5476632"32766'&'&"&&54632'"2654.'4547654''3'&5457632'454'&5477632'7''&553'4553'531#5335##"'&5467632"#&&547>32"232767654&'&&&6"'&'&5467232"&#"276654'&'&&547632#"'&547632#"'&547632#"'&547632#"&'&'&'.676767623"''7654'45&&"&&54632##"&&54632#7#"&4632#"&4632&'&'&'&'&547>730'7654'65&'#.6.6#"'.547632"'&&54767632""32327654'&'&"&4632"#"'&'462#0"5"32654&&264&#""&462'"2654.&&66"'&&5476632"32766'&'&#"'&547632#"'&547632#"'&547632#"'&547632&'&'&5477677632&'&#"'7676676''"1"&4620#"1"&4620'#"&4632#"&4632"'&'&547767767632&'&#"7637667654''7"264&""&&54632'"2654."&462"&462"&462"&462&'&541327654'4153#"'4547654''3'&5457632'454'&5477632'7'&547632#"'&547632#"7'&553'4553&&>&>#"'&547632#"'&547632&.6.6.6.653#5335#'2A*"&2B)"z0B$, -= 1*3)0 @/3)/ ;, .zP{PA4=S< A5B S=0K.;2? 9P?3"*B1'*A24*#"99X B0&7!!)3=% 0(6+(RuS)+67N#>$7N$=i2##2#;* zOzO 2<'(D>3;'(D#  #) w  !   " %&'2A*"&2B)"z0B$, -= 1*3)0 @/3)/ ;, .zP{PA4=S< A5B S=0K.;2? 9P?3''''.''''&";)   +   3 "c   8#           2#6$  ' "5 6 C,  !.&"   "*B1'*A24*#"99X B0&7!!)3=% 0(rPP93$6+(RuS)+67N#>$7N$=. P2##2#;* zOzO 2<'(D>3;'(DV''''2''''  % &.# ("         C%&&     #  % ! '  &" !    GH - - 6#  #) h    $  !   **** %!!!****7!!!!     3")$> 2#)$>4F1' F.;#"9/E"9-COZlZm',I$)#2 $-(#3?%)#.G61G,"($=1' 9"!3/BB/3!>+0>,1m#2""2K*   Y lZlX<CX)E'I+'Q&+56&+56"  #n*         !#"  #"  3")$> 2#)$>4F1' F.;#"9/E"9-COZlZm',I$)#2 $-(#3?%)#.G61G,"($=1' 9"@[@).D!3/BB/3!>+0>,1h . %#2""2K*   Y lZlX<CX)E'I+'Q&+56&+56 !! !! !! !!6! % #  " (4&&    M$  % &! A(# 8 . .%"  #n*  M |           " ""!"" ""!"!#"  #" " " " "+" "# "" "# " " # " N    MZ2?Ry1#"''#0#"&&'&5454767&547654'&'4'&'##"'#"'&'&547677675&'&''#"'&'&5476776767654'45&'&5&'&554'#&'#"'&'&'#&'&'&'&'&'5"5&5454'45&5476767&'&'&54776303276767676763267676767676767272767672367232673367"'4&&67654'454'#"'&&'&6'654'.'&'&'"&'&&'#"'&67676'&&'&547654'&6'"#"#"'&767654'&'&'454''&'&'.'&6'&'.'.'&54767&&'&667667>7>76376676767>7667>6##"3276545&              #   '    #   5 !  * ? %          #"       8         8  #  ;+   q  $!  !&'  $ !     !    )-   ( ")   *   < !)*!"/V0%!$ $"   !",+ ( &$*+  ' #5@ +2 .  ! T 05 ''  $    & D&!4 5       ")C Ew[=Qs'%&'&#"676'&&#"##"#"&#"#"&'&#"3273273232767676457332327>'&''66'.#&#&#".76766773277327276676677>22327676#"##"#&'&5476323"#"'&6&'6767454'&&#"#"#"&#"#"&'&#"367672327276676732232766&'&'&765232''654'&56765454545&'&'632'&'&'&76617&547654'&56323"&'67667676#"#"&&'&54754'4'&'&#"67767>&'&&#"676322&17#"'&6676773  ;$)$j<54 60&'C:R 6  #<$%.+  90/KAOFD"!!5+ /774&#9=%  8!   '5  &$( Q '*CG"!`6635,*,=4G 2: # '&,  /(8 (%&  G1U8E,(7 !v./ <\> #"+$- &   2    #8+<$C8!E   '! Ldq4-5 #:hd?:7   2104|  o&%)  %L5)!! !4) c    ( KL   #*WD*'&/" *%3 0TV&"  Fv '<g!3!''5!#%6323232#"#"##7232126545454�#0#"254'.54546303232&#"#"#"'7#5232#"#7""+3232767676561454'&'&'&'�#"#"'5632#7""33255325&#"#"#&'"1"302332650414&'.54332554'&#"##U{0  ]         !0".tn) '$+#  $Z 5U  S    "  e   1m!obiQ)  ,+ (> =H\!!!!"#"#"'&5543254&&'.5463232'�&3254&&#"'43632#""###"5c""l*!:/$ !() 6.!S#A'7HDSB8  V+*/6   %1 2+I 5E9BBK>Cx3qr/Feo#-9Paq(<JYr*c!?i  B Q d p     " -%&'"&4#&'4'&1&'&5&547667632>63232767676&'&#"'&'#"#&'&'4&5#"'"#"'&'&5055&'556'&#"1#0#&'&5&54766763267>632327677676##&'&'"#"##"'&'&'""#"'&'&'&'5545&5#"'#"'332765654''73277223630333276554'&757654'&'&''363365654'&##"'##&#"#'&&#&#"0327654'4454'&545476763221200'7&'"4&#&'4'&&'&5&547667632>63232367676&'"&#"'&'"#"#&'&'445#"'"1"'&'&5415&''56'&#"#0#&'&5&54766763267>632327677676##&'&'"#&##"'&'&'"##"'&'&'&'554&5#"'#"'332767654''73277272333276'54'&757654'&'&''36;367454'&###"'#'&#"'&&##&#"2127654504'4'&54547676320321'%67"#"'&54"1"'55&54757677#'5654'#'&'&547667632632377654'63223>&'&23#"'4&'667""#&6#0327"#"#4&5&#".7&'#"'&&'"1"'654'.3#&#"#"#&'454563232"#&'4547632323#"1##&#"#'03273"##632#"'&'4547672'"0#"'&'&5476773212'7674'&#"#"'&&767#02&#"16654'"'45&56'263&#"'&#"#576654'&''545&1'3276'5723637'475767#"'56"'#55<5432##'32327&5&54&#"&'&545632'&'#5&54567232'5&547454'�#"&&'44566&'&''5654556721#''&#""#"'56"'#563#'327454'5&#"456"#&''35445632'455&#"'"#"'4656"''5645562"'323665&'"6&56"#"'0#"'#5454545472#'3273&'4'&#"&'676'&'&'5456763'44&5454&'"'66767322""#"&7674&&'&*##&##"'&5477>56."'##"'&47767676732232##&'3367676/&'57&##53676767673:3>705463033212""#&""#"&7232767654'&#"#"0#"'&776767"54763213232"+&+'2;656674676736767454'&#"1#0#"17#0#"'&774776303232'232767654'&'&#"#"1">7054663033212"#&""#"&7232767654'&#"#"0#"'&756767054763033212"+&##'2;676656656376767654'&#"1#"1"17#"1"'&7734776323232'232767654'&'&#"#"0&".'&5&&67>7632&#"07.'654''&''3454553'0545&'&'&'00'&'54451>3'45&#&'054'216722&#"232"#".'654'&&'&'55654'&''754'45'3236573223###&#"#'5'5676327465632&#" 63    # +,!# *+tb,-   B % '&J"    # +- % )+ub+*   " %  ##>  #%   #  A   *,`s,("+*$    & | 73    # +,/$+ *+ub,,  A %  '&K!    $ ,-& )+ub++  !&  $">  #%  $  A   +,`s,)#++#   ' ! #  8 ' (( 8M.3gQe  UG[   :,#  #  -%]              (                        7      ~     e        I  5    0I     I@     I  5    0I     I@    $%  *&8    =   $       &. +&)+A:9=.  2   --   @    "%@4           --   @  #&A5=    )       "> 2>$! ?    ~-+  l  1   --  @  "%@4'*         -.   @  #'A5>   )      "?3=$! ?    ~.*  `  B    &!  5gr ,/:4A  iz.. 5   ##!!&, & 0 !  %)   # * (         7 52  6/!,2   (% (0#      ' .     !                O  $ %    +   "!     +0  L   Z""     ""8    F+0  L  [""      ""8     :gy6   92:      : [%?Pe#3232767654'&#"3232#"##33632#7654'&#"###%#3232767654'&#"3232#"##2, A/" $>$G?& I 9$H1, @/!% >q! J!#0! XX  ! J!#0! X uw~%"'&&67#"&'327&'3&'&'4547&73.'67&7676.>7&6766776736766'67#766'.'&'4#14'.'4&"2'4&"3266faZ  + ' ,  &`(($  /1E( & #  ,%2!A5  +'Y A " 9(+( $'  ( c  o&&/&  >F [j64662"&&4662"&%7654'&#"##7054##"&'&#"##"0332773232767673327732332'#"'#732%&#"##"033277323276767654#"##732%#3232767654'&#"73232#"##33232#7654'&#"##%#3232767654'&#"73232#"##>vvvdppp$ 2 $`-2 &  2"3$a-2 %ER"}-`!+ 2 , 2 (21"}-`!+ ~j==j~j==otb99btb99Wl5r8 .f^ R 98$ * S  '`   " =$=lf`   " =?v8r~ &'%&&5476632#"'&#"1"#"3021327632"326677454'�###"#"&'663023212325677654&'%&"&463032'3264&"'%&&5476632#"'&#"1"#"302127232"32677#"#"&&5663022127654&'%&32327212#"'%.5476632#"'�""1"'7 *  !2+>>+  \* \ 6NN6>) 5&&@  w    )>6N$<$  \  [ 'B'T;B-  Q9\   +A9Q .. *   * - &=++< P   L67M0, 7%5%5;3  ./L6$<# /  &A';S2,9P  .1P!>w ;}6632"&5476326654'&#"###"'&'476776765#27445&'&'&&'&&'&'&547676767676?676767>767&'&'&'&'&5474545676767676767676767632367654&"32#&'&'&'&'&'&&'&&'&'&'&'4'65476767676767676767676767676767671.'&'"#"#"'&'&'&'454756767&'&'3276767667676763232` )):)6  :  9 4 %(      %V  *$    ;-):))X /.30/(       $* 1AWJ  3;      (/03./ 23KHnXC,9  &  * 2 +!##1M):))   i 8   NaQ?         .    8(    )):) !":?            (!<0B3       ?:!!  H6Jap,,$)2    >=s!*.'&&>76767676323276203266767676#""#""#"'&54767762#"'&#""'&'&505454&&q;: ?? 13)  -_(D GF 7 ,-  KRR #% %$ & @$' bd     =0(%3P^2####".5'2&>34#"3266".'544>274&#"33'2652###".'52&>314"266 /B ] !  8$u    t! !  !   .C ] !!!8$t   (   K  O  _!!  'H     K  O   $> !%)-159=AEIV`m}Fy%'''7%7'7''7'7''''7%7'''7%7'''5'5'%''55'#'#'!3!577757!#'#'346332##"&5#"554332'"332554#46332##"&5#"554332'"332554#46332##"&5#"554332'"332554#&'&'&'&5474767676767<61"'&'&'&54547676767676705432031&'&'&'4147676367654'&'446654&&'&'67676"'&7654'&'&'676763227"03676541&'&'476765"#"{֪۱Уп^IYY`KeSCX֪ڰФ٨rߢӸ\\^__\XX^ H%JN QfC Q&&-& & 0&''-' ' 1'''-' ' 1')      (     (   > " 2  " `e-]5/:==.9 6+1`e-]b^/^\oo$g;;>==89`````o-Itc``a^0)`^^^]& & & 4&&& & & 4&&& & & 4&&                        _    # # !   "     " }vk-;Hdr~#'77&5662#66'.#"732#"&&5461264&#"67".'4>7'01"&546266&&'&#"'12"&546326654&#"*D>$A_jX EC, dc*7J s  "CFWi`@$?E)+O J7$8cK' 'fS-Z L:% Sgi  * .=,Se?7Y<']"igS /#:L+ /x 2>B[x223!"#66%3""##&&'3#6623!233234453223!3"##"#445!"&55323:327767'&&553232232767#"'&'.553232232767#"#""#"&462"vhwF)`E%;aY^_Y?kY^_Y98:8 V[[U PTV‰$#$#%$ij('`#qq5&C  C*  P  P   T  TL55L5 /x 2>B[x223!"#66%3""##&&'3#6623!233234453223!3"##"#445!"&55323:327767'&&553232232767#"'&'.553232232767#"#""#"&462"vhwF)`E%;aY^_Y?kY^_Y98:8 V[[U PTV‰$#$#%$ij('`#qq5&C  C*  P  P   T  TL55L5 /x 2>B[x223!"#66%3""##&&'3#6623!233234453223!3"##"#445!"&55323:327767'&&553232232767#"'&'.553232232767#"#""#"&462"vhwF)`E%;aY^_Y?kY^_Y98:8 V[[U PTV‰$#$#%$ij('`#qq5&C  C*  P  P   T  TL55L5 >W 2>Ba~623!"'66%3"##&&'3#663!33276453227!3##"'&45!7&&55323:32767#"#""#"&&553232232767#"'&"&553323:327767462"uiw F(aE%U[[UPUUOY__X89:8`Y__Y?k׉#$$!"% ii''`&pp  S  S    O   O   6%C   B*L66L5v+?[j&&553272232767#"#*#"&&553232232767#"#"#"'&".55033232232776746632"& )&#ryyq#&)jqri)&w~ ~v&)'*IJKI*?tS1w~ vStSU?tS1SC" o )( o "%# j ** j # -Y ** Y!8!B8! -!8!!>v !!!!!!!!!!!!!!Z?v`-E[t )8ES`q"-:DNXbl{o2DGJOS #')14:AGMQW\agrw}#).3;CHOSYahnuz (2Lf%632"#"&'&546&&#"7%6654%632"#"&'&546&&#"7%6654%632"#"&'&546"&'&5467%632327%6654'.#"%632#"&'&54627%6654'&&#"#%632#"&'4546%'45467%632"&54763227654'&#"72#"'&5467#"&54763227654'&#"712"&54667"&547632"&54767632#5"32654'"&5476322654'&#"72#"&&5467#"&&547632327654'&#"72#"&46'7"1"'"1"''70##"'321767415'770#"'"5'71#0'32576705'77#0#"'"''710'321765'70#"'"''7#"'303725'7#"'"5'3700#0'321727415'#"'&'323730320#"#"541430776764''&'&#"#'44563236767610327&54767632654'&'&#"3276"#&5&543176764''&'&#"#1"'454637367672361#7&54767632#"'&'3257212##'0#"123767673366'4###"'&''732767654'&'&#"'&47677654'"0##"""3763270#"'&'&54767632'"32767654'&'7'7'7'77'7'7'77'737'7'77'777''777'7''7'7''7"#"'&'#''65'7''67#"#"#70''77'&7&7'&7&"1&7#"1&7'&7'&74='67'0276716''###3#773#''7632'767232'7654'&#"##5'7>7'7'67'>73'673'667#7367'73'7'673'667'73#673'7'67'67'67'67'67'673'67'67'67'67&'66731>7173166701667317316717167'>7#7567#673#7317367367173167167#36721#30670'67167'7#67#46741673"167'6701673#66725671#7'670'6701467'>7"167'670''77'7'7'77'7'7'7'77'77'7'7'5'77'7'7'7'5'#67#6'76323'7454'&#"7414#"7677677414#"7677675414'&#"'73072773727'&7'&7'&7'&563277777717u`iI Tu`iI T  s^v^sl _s Ad FRh dWRK d E S b     v       u       p      u    BAp  )  )     (   '   +   + z        W 4H4(5B]/ A(9V 6G51 I2 V7*21> =0E9-'   !)&! j )&))m/ !2-/"  2 6-  (0'  1"0#3%2R,"-0331,,033-/232-,     F= = - E/   ,&PZLE NPZLE NN  XAiBNW X7 iD   L MEDD   L  !(R%EG -  ,     ,  ,   ,  -  ,      -,   )\_\`^\ \_\`^\ \^\`^\/\]\_\[bdbebay   '_$ t      $_#    '(    $_          Z &-  V '   %+  /(  9YKFR a%(##%   N E(,! &< tu i B  $B  D  0-111WNi        ))                                % GMMQ3J; %%% KSg  Sg  Bg  H@  X  c U [ L>[ "%(+.147:=@CFILORU''3#3#3'73'77#3#7#3#773'77#33'3'#7#13'73'3#7WWXeWWXXW WXWXXWWXXXWWWXWXWXXWWXXWXWXXWXXWWXWXWWX,Ǘ^^9/^0 >m#/;Wb3"!4&#2657677'&''!7&'567!!!&'6#'7''766326#"3277#"##"'&545'45476323232'654'&#""1"577''#'7/7''773'77''77#5            5 [@ 4     0  59IN4N(O'KI,      F   5D-!BAE %=se  A @aFȉOEHq<HN2JEWXaim+EOXe Wdqz 3""#""'"23##"1#"'""#""##"##25"#6701672612763#252501232#"'"#"'#"#&5#"'6361""1"#4#"'"1""'&"#'&'&&'&'&'&&'&'&&'&/01#0&5'�'"1"541"&147"4'&'&'&&'.'&'232123207&&5'03074&'22670017&'2223003263322232127232322327236636672663232367663061>767>7636767>7676767674167"'27C&775261+&507&7034745"#6"163#6722305&'267"&"""#"#""#*""#01#*#"#"##"#&""#"#"'"'&'"#"#4575#05'615&72672236727223663232232632327654530367#50<1445654'07&4&'405'054565&654'54547676545654'#4&5"754'416&5643765455#454'&54654&5065445667470&10"104𐡖#041676'7466703067676776767667667667667067667>766766765#"&#"#1#5212&&521474'47714'1264'"'#0#&'061&'&'7&#&54'074'654#&74544&5&'445&54&5341&5057&5&545025476767076767676766767676767676726367266763672321233&&'3''""77""'"16""#75"#"1*#0#5701"7667227272761276323&'&&'&'&&'&'&&'&3063&'2166701470#1'134'465&4&5454'&'�#""##21%6767>76766767&""#67676766767676670#"'67765054767054''&'&'&&'&'&'&#&#""#"1"#"##67631'467#66'"#0104'1"'0&1#0163&'&'&'&'05&&5&'0"5.'&'4&1&'&'4147625'434503277&'323276'&'17&5%6'&'03&'.'&'&&'&'&'&&'&'&'&'425&6'#0##3&'63&520#"#'#21'36565127263632327067576545454'4'44541"436'44#44'&&'&'&&'&'4'&'&&'&'&'&'&'&'&'"&'&''"'32103011216&73674'5470&5##6&5466<7&'0165676&745470&14767#4'&'517#&16'&70'14&7014450305667067014&.'6370166722123330"#46767706335667&104567761475261650'6672"#7#"'67662234613#&'&5477&&5267"166670457"'#2011#&54.'#/&547'"&&''&6675&''77>7&&>77623>6'%3276654.#"                  " !  ! '      #          "#   2B ;   ,6) P 3          7*     f           !$    .TJ bV 7  < "4'(8 ;''4''&#$):C            "    %     - !    %                    zW !           f;          (      (22   0  J             !        uf  q(' 9 1 8    9 2 8q 2%:)>wW`in-GR[h _ly3"#""'"23##0#"'"1#""#'#25"#6701672612763#252501232"##"'"#"'#"#&5#"'6361""1"#4#"'"1""'&"#'&'&&'&'&'&&'&'&&'&/0#4&5'0#"1'"1"541""147"4'&'&'&&'.'&'232123203&&5'030614&'52674217&'222300232273232127232323232236636672667232367667061>767667636767>7676767670567"'27C&375261+&507&714345"'6&163#6726341ʛ"&"""#"#"#""#""0#1"#"#"#"##"#"&"#"#"'"'&'"#"54575#05'2125&766322363672232632232232236327654530367#5<1445654'07&4&'405'054565&654&154547676545654'#4&5"754'416&5641765455#454'&54654&5065445464647470&10"104𐡖#041676'7466703067676776767667667667667067667>766766765#"1"'"#1#5212&&52474'06114'"664'"'0#0#&'75&'&'7"#&54'074'654'&74544&5&'445&54&5341"5057&5&5414347676707676767676676767676767672636726676367232123&&'#23''""77""'"123"#75"#"1*#0#5701"7667227272761276323&'.'&'.'&'&&'&463&'2126701470#2'134'465&4&5454'&'�#""##21%6767>76766767""#"#676767>76767667"'66765454767054''&'&'.'&'&'&#"'""#0#"#"#127631'265#"106'#001&'13"'050'0167&'0&'&'&'41&'.5&'0"5.'&''5&'&'05476241'03430&501277&'323276'&'17&5!6'&'03&'&&'&'&&'&'&'.'&'&'&'065&6'#0##3&'63&5200#2"#'#2'3653651272636323270670576545454'4'44541"436'44#44'&&'&'&&'&'4'&'&&'&'&'&'&'&'&'&&'&''"'32103"0216&73674'5470&1##6&5466<7&'0125676&745470&14767#0&&'517#&16'&70#014&&701045001667670'&&'63701667221233330"#46767706735667&154566725461261650'2671"7#"'67662234613#&'&5477&&5267"166674457"#"#2011#"4                    " ! ! '      #          "#    2 K ;   ,6)  O 3           7*    f            !$   .UJ bV C           #   %     - !   %                  zW            f;     (     (22     0 J             !        t f  q>5<O_2 32"#"#%%6723#"'3#3#!!#50532766&'&&'6#&7C&%  8i "'HF4KgU& 5L; #   Y ``# cOOwcf!9'(2(O^DK4Ku BH6 "B >3J\m~%5466;2'##"&&55%#!&'&50=4767!2##&'&'&50=47!2#!&50=47!2#!&50=47!2%!2#!"&'>#35;27676545454'&#"532#"#P,lmRk@lm ml OA7   OA0  OA0  OAfu b00 "6+0 )ml@kQQQjmllmN  jwc $O jwc  wjwc  wjwct  cW  zT) 3N0Jc},F!232!"#"&54766!"#"'&547667!232303!667654&#"#!"#"'&547667!232"303!667654&#"2$$7654'&'&'&'&'&$#"32$76767676767676767654&$$#"$7"332$$$$#2767667654'&'&'&'&#"3%264&##"3!"#"&54776$%!232!"#"&547> l{*{* {+l{+{ l){ z)lz){ l%{m&<>)'G%,2^Hh@Фw?m/-?vYdWq* VE+#2!> T"L^  ">w1B[t0#"'&547667!032!032!0#"&547663#&&54667#"&547%5'&54632654''&#"327%6"33264&#c   r   p      & ] =   <           3N0Jc}#3M!232!"#"&54766!"#"'&547667!232303!667654&#"#!"#"'&547667!232"303!667654&#"2$$7654'&'&'&'&'&$#"4.''32$76767676767676767654&$$#"$7"332$$$$#2767667654'&'&'&'&#"3%264&##"3!"#"&54776$%!232!"#"&547> l{*{* {+l{+{ l){ z)lz){ l%{m&<>)'G%,2^Hh@Фw?m/,A@?vYdWq* VE+#2!> T"L^  ">w1B[t0#"'&547667!032!032!0#"&547663#&&54667#"&547%5'&54632654''&#"327%6"33264&#c   r   p      & ] =   <           3N0Jc}#3M!232!"#"&54766!"#"'&547667!232303!667654&#"#!"#"'&547667!232"303!667654&#"2$$7654'&'&'&'&'&$#"4.''32$76767676767676767654&$$#"$7"332$$$$#2767667654'&'&'&'&#"3%264&##"3!"#"&54776$%!232!"#"&547> l{*{* {+l{+{ l){ z)lz){ l%{m&<>)'G%,2^Hh@Фw?m/,A@?vYdWq* VE+#2!> T"L^  ">w1B[t0#"'&547667!032!032!0#"&547663#&&54667#"&547%5'&54632654''&#"327%6"33264&#c   r   p      & ] =   <           >w1B[t0#"'&547667!032!032!0#"&547663#&&54667#"&547%5'&54632654''&#"327%6"33264&#c   r   p      & ] =   <           >w1B[t0#"'&547667!032!032!0#"&547663#&&54667#"&547%5'&54632654''&#"327%6"33264&#c   r   p      & ] =   <           >v/J_%"'&4762"2764'&2"'&473232#"##3232766505454&'&#"#>k>kb..PPPg *# k>k>g..8PP<  wZ )}k %1?KWcs+;GS_kw32##"&4632##"&514632##"&46#32##"&46#32##"&514632##"&4632##"&467!2#!"&46#321##"&514632##"&4632##"&4632##"&5146321##"&514632##"&46#32##"&5146!32##"&46!2#!"&46#32##"&46#32##"&46%321##"&5146!32##"&5146!2#!"&46#32##"&46321##"&514632##"&46!2#!"&46!32##"&4632##"&46332##"&46'32##"&4632##"&4632##"&467321##"&514632##"&4632##"&46!2#!"&46#32##"&4632##"&46!32##"&5146321##"&514632##"&46# #   l l Ϗ  Ϗ      f  k k # # # # G G G G $ $ Ϗ  I    k k d$  $ I    sA  d$ $     [ T   G G G G k k k k +G G G G +$ $ $ $ A  G G &    k k k k                           S      S                         H            G   f       G   H              H   v[(%654'&#"323%#"''&5476323   5D  +( `   -%%"#"'!567&'532632"6654&&534553] 0$HXE aa I `wxLA); v!0'&4557>#673ZSq42D;<*5;('2-,po "=+#P\]Zv"%7762%77627mJf|_J ?+p H eV dqw/g /Xm (08@HPX`m&&>&&3266'#".7>32232'#"'"#"'&&5441476'632632632&54625454&"454&"&546326&54767&54716&54567&54516&545167&5456%6545&'6505&654'&'650'&6505&'6545&$264&"462"&462"&264&"6264&"462"&462"&264&"4&5>321Gޠ1G!LPI:#k%G2+?gSTd48Wl:pU08R^ ^R70S. =#8>=7#= -):)*6*)  8 ! 8  M l=>RvFcFFc"0""0    FcFFc"0""0    D(CRޠ1Gޠ1%?oIQzU09hYSS-^!50.-05!}^S'33&))) +       t *          |  cFFcE1""1"+7  %cFFcE1""1"+7  l&lv( C"&5!7!5!'!&'&'0&'4>'654454676777/vS\,./  / )&  ;  ,.8kE00WU,5C6G!O7E#A)FQ"A&Ca0<*CQG6 H }vk !'-3%%%%57'777'77'WVwW+iGR,OX,`gqPaqh+g0.s+.2&8n8<m>v#-H".4>2/##32>7'663253&''7&'#5'3#!5#FuuFFuuFԕWf*7"#C YuEF}X"A5(6>W..[~~[..W>??,YuFFuuFFuYXD";EuYWz&4@" ~[..W>?W..[~II>;F[n}336&>767:#"'""#&##&$'>3"&3#3#3#3#30532766&'&&6#&;E !!    32"6&5475&'276767767&672767#"'#"'&&".556''6766&&'767#"'&547667654667.'&76"&&7>3&462"6264&"&'''476'06%&&54&'0"1"6303#'&#67##"'#&'32%32673#"%67632&662&#&&'"#""6&'&#"''7%&&'&&''&'.'&'&%&&'.5&66766'60760'&%561>'&764242&42242424423⁆,pyq%) 9   '&s| b,%##2##2 F.' + M+ <  (&4jS  $%&# !S00T  5# a1   !$ '#?& $%&$ '= %   #%5 m*# , #2$$2  P%+'I#++#q)24)$98  $  "  ('"  # %K  "?T@"**,"J6"  !X+& E!   6J=BY&+$G *    # Z--!%##($0 6125 0F'  %"$#3 63  '&I2##2$_ ) ) /  %"$$3 #-- *1   .#0#%+,K'))'[  ? ` t7)1   /"2 L#"!$ O  '  1 '3!  k    2##2$_ , / &)'..'^ ,3"   R]$_X $$ A E>8(8Z TQpM;T Z8(8 >E +xMp    " " " " v#!375'#!%355#klkkWWkkkkWWDWW8  !!'!5!75'!'7'%'764DDD,DDDd- --- 4@@@@`@@?-  ---  >A<j%"#""&&'&'&'&'&&'&&763232767576765&#"#"'.'&'&&'&'&#"'&'&547632&5476766763265&'"#"#"&&'&'&'&'&'&&76322327675767676545&5477667'#"#"#"1"'&'&'&547>76767677667'7656545&541##".''&'.745>0#"'#1"&'&''&'&&54745676732326545.'"332>76767#&'&'6323232#&'&'./4547672323667&''&'&#"#"#"j 1&Hg^IM & !  %U  .&1 H pby )!  nbHL&#  1&If^IM! & ! %T  .!% V!.k-_o.  #N)%|.k$$!V!*D- " '+     *B <#&A" $=E>0 " -M2)B*     +">2+4g)*?4&&%#Y) f :c!$R4?+(g4+  +)Lyn]b+ #    `-    q + tLQ % +)Mwo]aI #    a/ ,:0  ;CKDB9      t=:;C92~   ?$-*'#',23.)  *&B9A- HB$&CK  .D:\'24%&* ).3 -' #O$ $ "29!5  r= 3-:$;2" $ >y:H~72##"#4.54>33!5#5&4>"3264&#"#".5754>7332>575322'#!37#26554&#"32/K+ #" )H  '  )}?XL߷}Ā?>+Kcy3!723277'676545654'&#"#&#"32'&5454547632632#"'#"%5#32327654'454'&#""'5763232#"%7'&'"#"#"#&'2327654'654'&'&'&54145476323UUzz> ,4*  %9:%  %9:%%$$f!63.$'   Z''"  ++'&#  +{|ID#+- ?6,-7?  >5+O(+-(  ), +(r'/ .(.!   .    >0:E''&'&#"'&'&547676727012%3264&"7"&462&%:= /7DImr#VKv79 p?OF:7hДhC^___,,13 #ST37mc'Z2R|f1 VѓѓZ___BB_>'Dd3!7'676545654'&#&#"32327767'"'&547535#5##332632#"#"#"'&547&547632UUzzX." ##UA)##"V10>>?!)((){|K'/2 F<01=F  E:/QO0 y?^^?$Q#-1 0+ !+/  1-#>'Dd3!7'676545654'&#&#"32327767'"'&547535#5##332632#"#"#"'&547&547632UUzzX." ##UA)##"V10>>?!)((){|K'/2 F<01=F  E:/QO0 y?^^?$Q#-1 0+ !+/  1-#>`Fd33533#3##5##5#535#5335#'#"#"1"&'&54545476630;2076545454'&&#"327`RSRSSSSRSRSSSSRSS<1H-7/Q;;Q/.Q;21%/0%%/^SSSRSRRRRRSRRS H=322323253454'&##"#"30367654t-,3/RLH6?"2-.$#78#&%"6:#(j#''#_y  pW#.U?5+,7C  A6*.8F >v'9FTfs2>4."#1"&462%&#"#677'654'632"'5667%&'7323&'&67#"'&547'6%2&'6756>FuuFFuu***<  4q)0'S !4=-+R /-Gs#"&0- 4q)0'R!!4>-+S U/-Gs#"&0-uFFuuFFu<**<*o(-`5*!0!i)!#&tb 0- <cB0: ` (-`5*!0!)!#&tb 0- <cB0: ` >v%-2>4."&&'632&5467#"'6>FuuFFuu'YHNYuE UmPF3bqm_7uFFuuFFu.EuY5|a=@531vL #&),/258;>ADGJNRVZ^bfjn!#!4667!#0"1!45463021!!7'7'#'7''7'7!77'7'77'7#''5'7!'7''&&!>77''7'77'''7'6'#677'5&''''"1"'6323/73754&'#353'66'#537#353354�##53#535#0#"33#30326675054&'#35#35#L:).M!5%4P>POMM76'5 (!# ( `.~hH/WULY'/6791 L 2 2"w 6 T(O u.' !,3&;$ w5>"#  D#*! ! "":!:: ?>  "U3i""'): .!%4/qO/.;.; NPp////ÿO R7 3m!#? 9 +- `K 6&%K>>&1/4 J U42  %%+" __ E#     .""Iv  #&),/258;>A''757'!5''71'771''71'77%'171'77%5'1'77pqrr qdssttststststtttttststststswi&0"/'#'367654154'&#!'YqONn# >*4O:898QW/QN  8G 77NQ:;Lnzr#""#"'&&547&546766323232#"#"#"#"'&'##"#"'&5454547636327332676545454&&'&#""#"32327.#"#"036767_hr6=?87T h/4I!\3D(?*,EEQH/ KA %@3&WtG/d3-`VPj '9--[1!* A)e2L U;:A^,uB rY%* &.2CmVV7*"$#HY %D:M93l @q*U! FDRq$*:6)JNQ!632##323276545454'&#"##'3'323276545454'&#"##'#'^ dIB!;Ry""y4' ""yE3l9FLd HA>$B.#"H@V#"G4'>'E`x462"#"'&'&'46763223232#"""#"3232327665&'&'&#""0#"'&&7676767626767676&'&#""'&'&'&547676#0"766'&'&'..@..@ ~>4)QG <2)ZL} >AFAw}|xCI,6} " KFj/;0# HCh0>H W4bAE  4*fC+BVlGJ (#o'E`x462"#"'&'&'46763223232#"""#"3232327665&'&'&#""0#"'&&7676767626767676&'&#""'&'&'&547676#0"766'&'&'..@..@ ~>4)QG <2)ZL} >AFAw}|xCI,6} " KFj/;0# HCh0>H W4bAE  4*fC+BVlGJ (#o'E`x462"#"'&'&'46763223232#"""#"3232327665&'&'&#""0#"'&&7676767626767676&'&#""'&'&'&547676#0"766'&'&'..@..@ ~>4)QG <2)ZL} >AFAw}|xCI,6} " KFj/;0# HCh0>H W4bAE  4*fC+BVlGJ (#ov*6!!3533'67654'&'�#23232#"##%!5#535#535>lTC2`F!)3J%> l^_o "* +B CEBCB>v '77557%zzzzzJJz<?R<R >v #3#%773#3#73#3#73#73#wxuB|$w6BBL {i-15=.>7>7'6>&&'!!!264&"$  ,#VoQoV#,  $\jwiY32232"JJ  uJ@!JJ &l9 %(=٢X#y}kQ&#"#"#336763632#5654'&#"#"01#54#0#"#3366302323266322327"II  4I I?II4 7!j %1?Ϧ "U"10"|>v+;?OSW[_cgkosw{64>2"&&%'&47764''&"'762"''&47762%'77"''&47762'7'71'71'71'7'77'77'71'7'77'7'77'7"''27764'&"'&47762&"'&462"''&47762"27764''&#"''&47762&"27764''>FuuFFuuZ 1  " 2 Z_oo**+78++*++******+++*++****+******+++*U**+++*U++*`  7  7 7 7 o   n q o oӲuFFuuFFuQZ "  1 2 !  Zaon*++78+**+***+++***+++****+++*+++**+**U*+++**T+**Y  , 8  7  P 7 7 o   n   o o>v#+L46767663!2#!"&'&'&&5$264&"%32732767654'#&#"> Vf:fV  VffV &V==V<  S fV '@=HfV Vf=V==V8   Gvfu|.5045054676620'5#"'&545654'&'67654'45476335#0#"#"#23232373533'665454##532#732##3232767676545&547676325"#"'&'&547454'&'&'&#"#usC;;;;Cs       ? .=%A!YP +==       tuX==DD==Xtt$ !  #  &  bllo'Hz_-2#  ! #  &  a6&#"#"#336763232#"#"55#53573#3232127"FF  Z33FJJ  Y &4@ V7G[7 >cF{%#"0#"'&'&5454'4147>76'&'&"1##"&56'4637232'"#"'&'&'&76763032303207226'&7667'5#76%#"&&1&'#"#"#6676764'&'627723276762%&'&'5&&'&/'&767654'&'&'4332>7>776766763(--    #55 D9  ( ![ Y 0 [8  p 5Ou(0?L# 6 - 8 +  B<>.(!D  **KK;      p!  E .  0  @  %) 4 g.> L  \ " 2Pp2 bq + 3hM?&4  GG >#?463!2##"&554&#!"0#"1&&5#!2#0#"&54154&##"&'46>Eu7'wIee,Z.P0 }Jlt22&7lJZ?1S4 eJ^v"&47676767676320#"#"'&'&'0##"32207233232"#"##"#"'&54566567463232332544'4654&&&5<547232323276767676766767#65454'&#"#02654546545454&#"161676767632#32767#"'&'4&'267676r60CWl(*   Y!9 (  DG,?+Ta$  "        -  D  ~ 1-)p]$ %           gZSDE =   ^S-&  /  F  %;      2 )#bu-  q   -(       >v&4Gc|2>4."46632"&54&""&5#!"&463!2'&5477632#1"'#!#"''&55477633!27'&#!"3!277654'>FuuFFuu'#1a   H H  5  55 G 6-EEE^EuFFuuFFu&1"     ~  1  1 55 F 55 4EFFF^uvs 37EUmq663223&5473264&#""#"'#6'4.'&5045!%#5232'45632#"'&54!#4'445#0#%3#1"b8rj >/8^F% 7  /?  !D:;*0E  ,8 7X,G^M?"0)* Rp   ^G  &  vC2e~#!63>767>.'&'0.766#!63>767>.'&'0.766>767>.'&'463!&&#"#"'&&>7>32&&766&&''&'&&>7> 32&#"COB?Pr1 - $-$ Ya %OB?Pr1 - $-$ Ya % ,56576776776676766726566767667>7667>7267767767767677373736767367637267367632673663763362765267227227""2332745454545&#"##53335#"'&'&'676767654'&'&'"1"#"67632#         $,p3A,+D+11  !)31@ "! &0     %   !!  " AA$HH  $ E?v"29;?CGJMQUY]aehknqtwz}7&54>73#"''654&'7#"&'732673''77'37'37377'373'7#'3'73#73'3'3#73''777777'3(EuY#+AwU3r$nj"B#z%G6Ԁ5&M)[`&duImIIu:f:I:u:I,,ffIu,f:I::Hu;,XHX;WWfWũ:6LRYuE:;3VwBA;; ypXRD47| ps3N^hUBy6<%:TJJ;;:II:>v1;:;v>~9G%".54662"32664&&#'&'&###!2'#32650454&#"YuFvvv4ZYYZ^TTD hO  (,/$&&NZZ3/Nm;POOO6\m\6)\\)   6I8Tvf'/=a#!".=&55675463!2!>'73'7!"&463!23!&&5533332776332323665M   E!u V% HM66MH# ) q & #n K !   K!!XrKzGQ""!  v6MM6vs# 2 2 #>x &38=EOTYbg%%'777%%77'7'?'#766'&6%'77".%&3%6&'572yϗ9y{Q HF^ 363Hp2-*Fe3.PdAu"/O^eOhCwW.?  ;"sF- (kIT& A0 &!K%% ( ++ * '  #    #  ' * ++ ( %% ( ++ * '  #    #  ' * ++ ) >-+ ! ::\ == E=R+*Q  1*9 )   #  ' * ++ ( %% ( ++ * '  #    #  ' * ++ ( %% ( ++ * '  # <(9Teh (5A'7&54632#"''"/7%632#"''&47&5476327#"'&547764''632#"'&5475"''&4776271376#"/>4''#'&47762032763276327632763272#'#"''#"''#"''#"''"/#"/#"/#"&&57'#"&5477'"&5477''&5477''&5477''&4?'&54?'&54?'&54637'&546327'4632776327763277630"1"065054&#"''723232776632367676''&5477&&0#"0032654'&"654&"65054&!"65054&OG+H3MM}F0 F;F< 1Z.G0H2Lp0@ +'S  5BN2!G* SD= !  (  .  /  01 . $))$ . 1 1 / ,  (  !  !  (  - /  11 . $))$ . 11  /  -  '  !  86 .aJ   @( = IRNG ?>D#'        DF+3I ML G 1G;F\*"1  .F03H L:1 @*CS 432G*   SE,)$ . 11  /  -  (  !  !  '  -  /  11 . $**$ . 11  / -  '  !  !  (  -  /  11 . $)W  00 /B  $I    ?5  F" F Ui*OE^    a    ;v .>7!!!! 7^ooOQxs|8> %*;5'5'57%5/35%57575757%7''7'#775Oy 4OARRNRqˈȾRlje&>>a& (*RKbc`cb_cb^ccecgFKhccb_h)6>v %55%5%5llllҫҪ(ӥҥ;"+%2'47676&&'&#"#"#&767&'&'"#"'&547676767&'&'5#"&77#"#"'&545671#"'.76767.'&67676#"'&'&76327>'&&1>7676726363202767476767667636767636654'6767&>365576654'08C +#             =$  i_=!,\r[Q@38"& 0'# Fu?!!*  "1   3  %  .    " | <&   $ ($+(!  7+$# !* ,$#*25@@$K*P&!#98v     TZ$  +  + r 06    ,4'*    D ?v=`#".4>2%0676654'&'.'&#""#"#"6'&&#"2327654'&'&547632YuEEuuFv!~0)0#   9 S2Lg j?715?&Yov%.64>2"&&4663!!"&7!!"&46!"&463>FuuFFuuD ӲuFFuuFFu  05*50*v!.;H76554'6554'4>554>554>55CCЁ2?LKQ CЁʨʨʨʨʨʨC%C$ C $C$ 33 3322)>W &7DTap}!.;JWft -:632#"'&54632"&54632#"'&54632#"'&54632#"'&54632#"'&54%6321"&54632#"&54632#"'&54%632#"'&47632#"'&54%632#"'&54632#"'&54632#"'&543632#"'&54632#"'&54!632#"'&54%632#"'&54632#"&&54%632#"'&54632#"'&54%632#"'&54232#"'&54632"&54632#"'&54%232#"'&54663632"&54%632#"&54%632#"'&54'6320"&54%632#"'&54632"&54632#"'&54632#"'&54232#"'&54>632#"'&54'632#"#&&54632#"'&547632"&54632#"'&54632#"'&54  ^   U           O          J        & C  *         M        - L   -     u  ?        H        5  3                                                                                                    >w.T{!#"'"#!&'&'&'56763235476766323#"#0#"'&'4'&'""'&546323#"'&&'&547674667232"#"'&547>7&'&509."1 .333)7  F.(7P 0 8'))"|e-    79  I 6  67 45  # S 1E*$-,D @,&t" '8'8     78 I 6& 65 34   # R >vk}2".466&&3267654'63276676&"#"&'.76766#"'&32766'&6767uFFuuFFu [t\-!-/? (C  $=  3)D8 8&0]C.&.?" &!84AQ - "+FuuFFuuv:#!'P*L8#, 5##&,1 &= ,/ZU$&%   w 2"wrm%67636'47676.'01"#"'.7>707.'&676766#"'&'&76327>'.1676654'"6,0KY  -IB"  Q0  /:&T><+)9*;".# >g!33%7322327676767654'&'&'&'&'&54767676763232&'&'&'&#"1#"#"1"'&%32327676##3632#0##Kl&   )  #$%#      G$#" >oZ\&R2 84C60J g    '# a       %QTAAlU'C+)?W01##6'3327654'&&#"#54614&'#"'&5477133&&''73&&''76632:e /!TSQaS)AK( 3!3F6kiP{s &%|!1!%\ߧ##l=I1U# 0] ]Al"@~Kzvo #*/8?DMTYbinw~%#%3'%%'5757'53'77''57573'53'77''5?'53'77''5?3'53'77/5757'53'77''57573'53'77''5?3'53'77''5?3'53'77'yj joz{nfikgkiikikfikgkiikikeikgkiikikeikgkiikikfikgkiikikfikgkiikikeikgkiikikeikgkiikikŞv;œ{=9A>v<z>:<z:>>=?v;z>:<{;>?>?v<z>:<{;>?>?w;z?;~<{;>?>?Nv<z>:<{;>?>?w;z?;~<{;?>>?v;z>:<{;>?>?w;z?;~<{;?>>?> 73#3#!!%!!7!!!>0k0R0N[08cIv !!%%'#'737%vJJ 2t55^4ii4\,DfD/ZZ^ZZ^N !!!!'7'7535!I QI>NPexq;;Bv@%#"&54675773277"77632776654&#":F]5-|#Iq ,|##|, qI#|-5]F9']9b~V'.&B2N(, U'.&AA&.'U ,'N2B&.'U b9]' > 77!3!773#'7!JJJJ S R "&:G%2>57%%'./%%%#%.76%$%0'&76%$1%#7#7##"43235&54627632#"'667#7#".724"/O3( ,?{NR"3gC8_?1JO4'!"(5|/=e q       5 R  VV O  )#$$0dg)&   T8It;J z  \[ # O Mv/]"#&'.'.'.'&5476767676323!%632!2#"'!"'&'&'4&'&'&'4547676676232"'&&'&&'223#"#"#&'&'&53232367&"#&&'&5476676676k0%GG',   *??V'  L%,$z)", (  IJ#HI  < >>"c   ' 79?>( CTH=ut'%&B+*4;NBgg!( &8H3.<$. 91yxH MM G c "#+" JI, 9\[$e B>vI64>2"&&#"1"#"'&547&54763232327&'�#"#"32732767'>FuuFFuud&&N'4 E4403F3+,LӲuFFuuFFuO#++"5' -3HD003!_v#*1<CLS^gqz#-6BNYdlrx*5?JS]fo{  ,4=IQ]hrz &0:AN\fu '2>HNXfr,6AKWdq$/<ENWclw    $ 0 < I U ` k w  ' 1 = J T _ j u   " . 7 = F P Y c l u    " . ; H T b o x    + 4 < H T a n {  &.6>GP]kz (.>LVds*34:l|Xbz $/7>HS^kx &5<FOY_hv~$0=IRX^jv(08@JT\dlt !+7=EOYaksz#*1?LVbp|!-9=DQYdpv}$+6BN^is*5>KYgo|%3@M[ku "-:GS_kx#3BJV`kx %3>JS[emu}   ! , 5 > L T ] j t z ! !!!"!+!6!>!F!M!V!_!e!m!v!677<56767676767#6''&&'"'67'&'7&'&'67'&'235"#67467.'6767'6767&'677&'677&327654'>74564'632"&547627654&#"633276676763226" #"#"&'&'&54767"37"!767"3767"6'&7"76'&3"726&&7433254'&64&276&"72&"35&#76&'73236'&3276&0>'4.27454'33254'4.267&'27454'736'32764&&'6762676&3276&2466&'&"%256&327614#"27054#"23&265456522276&"2634#&73254'&2276'276'6'&#">5%&%762766&&5&3276&1"302766&5"2764&5""%6'&6'4&6'&36'&7&326545327654#"764&764&764&65&&76'&"3227654'&&76'&#"267&2766456'&670'""76'&6'&076'&#"6'&6'&#"26345."7066'&">3"5.">70'4&&"37464&#&323&7"7636545&"36545&#"336545"&"36545&"36545&"36&'"76545&#"6'&136'&6545&#"36'&654'&#"367&&"36'"30367&323676'&376&&76'&76&&76&6'6&6'6&76&&6'4#"6'4#"76&&76&&76&&76&&6'54#"76&&6772'&"376'&"76'&#"3232'&56&776'&076'&#"033254'&73254'&33254'&30323254'&7454#"323254'&3276'&3276&&327654#"67&776&3276&6'&#"6'&3276&76&76&376&676&16'4&6'&#"36'&6'4#"3327654#"332766&673254'47<&&#&#"723276'""327664&5&61064&&#&#"73276'"#"776'&72&3234545"2236'&#"73276'&67654#"2236'&#"733254'&&33254'&&3325454&033254'&"33254'&"7732545&276&736'&733275&&"33277&&"73367&&"732>7"&"7326647"&&"332667"&"332665"&"3>5"&"3665&'6745"&&77654'&3276&"73254'4&76&76'&330326'&#"76'&3254'4776'&76&"33276&"776'&776'&776'&76&"776'&744&&#&361&'36&'36&'736501&'36&'7036501&'36&'73>&'&#"2767&'32767&':667414#"2765&&"32765&&"22677&#"77.7454'27454'327454'327454'327414'31276'2276'1276'32276'76'276'676'276'276'276'6537654#"327054#"3327054#"3766&65054#"7.#0#"77&&'4&"7&#"#"77'&&""37&5&&"37'4&""37'&&"375&#"324#"75&#"7654'663<&&#&73254'4&324#"276&"33254'&#"2276&"732545&#"32545&#"2663."707&'"227654#"1"727&'764&&'167&'4677&'"37&'"#37&'"367&'"!674'"3674'"674'"367&'"367&'767&'&&37&'"77&'7&5&&77&'"37&&1&"3654'&#"36545&"36545&#"36545&"36545&"336545&"3654'&"3654'&#"654'&"36'&"3136545&#"6'&"#336'&6'&"6&'%666'&#"76&&276&"26705.&7676&&7">7054&#"66'&&""66'&66'&&"6'414#"6'414#"1>'6&26634."7>'6&76&&36545&"3654'&"36545&#"36545&#"36545&#"36545&#"062655&#"02>74&5&#"06267'&#"67&1&#"76'&#"766<5&#"76'&#"76'"#"1676&76'4&656&76'&76&&76&&376&&76'&76&&3276&3276&327654#"13325014#"#43>'4#"67&36'&"327654'&#"32745""3765&#"327654'"#"33276545&#"34545&#"72'&#"36'&#"33654'&#"36'"372'&#"327654''"73276'&76&7>1&'77277&'7767"&&303670&&07>5&'0767&'367&'737"&"3276&36'&776'&772661&&7676&6'&3&"34'&#&327".#"73&27&'&#"033'&#"3'&#"021&'"1"3&'&73'"1"734&&5"#"73&5"#"3#"'632232#0"#!"#".'&'&5476367667662654&'&#"30212 3667676'&&#&#"#"'&'&&'""'!"#"&'&'&'&547676767676632232%!232676767654'&#"#&'&'&'5&&#""33'&&'&'&'4'&5476763323276676763233:3632+"3032336676707416'&#"#*#0#"#"##"'&'5&'#""'.5467632"32767654&632"'&5466&''&'&'3&'27"#7#6767327&'06767&'767&'74>5#4&53&'&'7&''67.'&'67&'&767&''>7&'56676#"'&546326'&3'&723'&%23723%6&76#'476&!6#&66#"#"&76.66"'&676#&636"#"&6266776"#"5476#"542&73"'45472#"'&4547&&6773'"&73"'&74>3"'&6"&&6762#"'454&62""&%6#4&476320#"'&76321#&&4%"#&67%&436"&3&62"76#"'&54%7"&&677"&'4467632'&54&547%&'&%'630"'&&6330#"'&44673"'&466623.%'&76'&5472'&467'&466&&67#"!632#"'&5476&676&676&6!6'47262'&&47262'먐'&4%"'&&667"&6'4543662310.476'&5476'&546321.76'&547632'&54&5436""762.&5436""7&5436"7&6%"#"7'662#&54762#&54763#&7632#&54762#&54&637632&'&76&'&76#&57632&'476"'47632&'&%22#&5463#&54"1"7670#"%&76'&76'&76'&76'&5476&&76&&76'&7632&&7632&&76'&76'&76'&76'&7632'&476'&367&632#&762'"762'"'6#"#"5476'&5476'"7632'"&&4476#"#"76#"#"76#"#"&&4676#"#".6632&747676#"#"766#"5476#"547632#"54&76&5476#"'&67632'&676'&676#"'&676&5&76'&76'&36&76&&676324&476'&6632#4&47632"#"5476#"5467&6#"'&7&5432272323#"#'66#"54&543221""&76323#"'&5476&76'&63"#"'&5445432*&76#"547632'&6&5432*&&76#"'"&76#"'"&76#"'"&662##"&&'43662##'476##&76"'&5476'"547620#"1"547620#0#"547620#"54762'"547623'"5476'#"547623'"547623'"54&&77&7637"7634&636#"76#"'&676&76&762##"5476'&476#"'&476&<66367<6636#"76&476&476'4<663676&6&6267#&&5473&632767#&&5477670#&&6327632#4&&3>7#&767#4&47632""#0&476#&762#&7632"#&54762'&71&&73"'45473"'45473#"'454730"'45473"&놡"놡'05477"꺅"&'<677"&&'44677&'44677"&'464677"&'64677&'44677&'446032&541472321#"'&5472320#"'&5476'&7632#'&54&543223722'&&6&543232702'&467020'&467:'&&66702'&467232'&&66!632#"'&54%232'&&6643&62""76#"'&547232#"'&54436&7632#"'&544646636"&7632#"'&47232#"'&54<6636#&763'&&444>3032'&77#&'6&6670'47670'477'3&'473&'673&'473&'4%3'6727'673&'673&'473&'476&'473&'472"&&56722&'072"&&'66324.4632#&54762#&54762#&54762#&54762"'&54762#&547632#&547632#&547632&'&50726"'&547632"'&7266&'&5076#&54726&'4&47%&4632&5476'&45>6"&&62"766&7632&'&&66322&'&&6636&&722&'67632&&76321&&760.6&5436""76.476'&62#&54762#&54762#&54762#&54762#&54762#&54762"#&547632"1&547632"#&5476321"#&547632&547632&547632&547632'4546&5036&76'&5476'&76'&76'&76'&76'&76'&76#"'&76#"'&7232#"5472321#"'&6632"4&&4&66&!62'"72323#"'447223#"'&5472323&'4547223"#"'&7223#"'&547223&'&'2323'&72323'"7632&'&5476'"&72323#&&&63#"74#"546&670#&7670#&76#&7660##"5467#"&&'4367#&'&67""&7623'"5476#"'&54&66""&&76&47661&76'&&>>36"3621"762"#"76#0>632"3632"3632*72323#66"76323#676323#672323#6      "     *t*    !        "Hfff*U( $*00*^B 7  #P&*/I II$     )     R      +        !   !  1^%&+ w 6G5  !  !  ` G  f ! 2  "' w  g  %  %   $& ( %      #     "        h H                     !  !        !   %# "      " $           "    #  " & %  "   & #  &'    # (   &      ! (   # #  # G # F     ( x& !   " %   #   #' # $  '     &  &  &  # % %  #! # # $        # " "    % " &" #" # 6   +   ) ) ! #  )    $     "  $ !   #       $        "  $  #  !  )$(" ##  !  # % #              $          #           "  & eK F    % 0#) !)    )  "#  J", "  " '(##] 6^ .-L3t.#"40S*)C&ff("-7 $F F  #+*&P#7t5P  %8-  !c97a  +0! N9/G  *2  Z4 /Q  40  H/'  !M%(,G a  ``   L0+(S$ ,&-XAKkkK B] O! =+                                                                                                                                                                                                                                                                                                                                                              # /) / $! 2A* (( ' &1/1J*HggH+K    %!O D4PO !*%  -42+!#! ^^b4E>.Q^^& (./% *% OO.<9#_;<   L :*  ;;_   ,@ "Rx1?#Cf kk\ ]=Ea?"'Db^B=.44.=(E-      ((          #z 2  -     M                                                                                                                                                                                                                                                                                                                                                              Iv 7''7'77'5RQ//geeEˊRR*U)bB]^]Z[1>y:Wo7#"''&55477632&#"327'&'53276324&&#"#"'76323265#"'&#"54#"'2654&"632&'#"'"''&'55677632632#"7 BB  w 5   [ BB         ݩ BB #  q2    F   pW"    "Wp w    W""[ A   AW""Wl6}v"-Kb %BVj7#&'&'&#&'&'563233232'3327#&'7##"#"#"'"'&'.'32767#"'"#&'33327676'#"'0#"67&'333276767'"&"#"67&'333276767#"'0#"67&'333276767##"'0#"67&'333276767#7132#"#"#"'&50453323232727#"''&5466332770#457#"''&54>332770#455#"#23277"6675#"#23277"6677#"''&5466332767#"''&5&332770#45#"#23277"66755#"#23277">123#'7027#'23#''77''&&77#"'#"'&'&'0414'.544546323232BC  C      &  $#<9HH!!NLQQNL87+'NLQQNLQQNL'    7LO RPLN RP IK FE IK FELN $$IGLN RP IK FE IK FEh +@ ,(!-> o-> |22w./46 42 h DsC c  #\  2$  LEc O  sgO$A    /*/(9<gCsD<9(/*/> %6h$4Q Td+^1I62322#"1"#&&'&&76766#"'&'&7676%6#"1&'&'&7676C;C;N^Z$!YH$  ]&YbO4u6e;0#/:=-,?G5!,6=1% ,>C5!v#/8<@HL^ ,4<@F{&''&''&"/%&''77%6323232#%&#"#3777#"&57"525##".5#3##"#"'+"&'&'&'&677'4677?+"&'&5&'&46677'4677&677.''&'#5;777676&/##'''&'&67654'6677763#"&&'532675####323276##3"&'532655+#321276#7#672#.635377&'454''7&''&'''303677321276557627&'#0"#"'2327#"'#"#"#"'##67''#"#"#"&'&'&''7&'&7'547716777#65&'&7654'#7654'&)  I  )-+&&,|z zz@'lY,$   $    &DDK %D %'!)5 !)4  %        7 8 Ip#              # "n )   N   MH /-JF #By# FK ; ca##\#c! 11#2 2O2"+)#+))+$( C##OU"!##I)+$( ,(OU"!#9 &$! t ,}+  C#$H,}+  F         ) "A:'@ CA(/  /(@A&!)''("! "4?*:CO # O-&H  &  .    v+AVh2#""1""&56632"&5.#"#"&463212"&4632212667462#"4623232#"&1"&&547632#"'"00"3I0E1w)1E0"9""00"NO"00"!:"0E1w1E0I3"00"w!#+#+0D0H3"11"w"11"!9!0D0Le0D0!9!"11"w"11"3H0D0`+$+$v !7!7!!'!!'7'7'狌獌r~~>4R"'&&547676327#&'&5477'&54767632"''&'&54776323q q          ,Y  U            , 3%5>B6z+%!"&&'5667!%"#"'&'#'33023232765454'&'&&'&'&5051476767630323273#&'&'&'�"#0#"001#"%3232767#01"#"'&'&'&'&'&5441454767676326765041054'&'&'&#"1"032121276%#&'&'"'&##303276767673#&'&'&'&#"1#03#5766765415054'&'&'5 +G*[A2A[*G            t (       a  (   B*H*A[[A*H*~ c       ]     & ,%%    (+ l s Zmvi!"&54>3463%&4''4'&''&'&'7&54767>767>767>&'>Z&' _:5K *     4#&(()0z.I,*?E#4' c`(^  N2'0C   *.  -  "108< )NE?4,+QzJ 9 &8hs`v+?[j&&553272232767#"#*#"&&553232232767#"#"#"'&".55033232232776746632"& )&#ryyq#&)jqri)&w~ ~v&)'*IJKI*?tS1w~ vStSU?tS1SC" o )( o "%# j ** j # -Y ** Y!8!B8! -!8!! ?8Zo"'&'&5&54767632#""#"'&'&54767632'#"'&'3233276654'&'&'767632'&#"'>&&7&&'6767676"'&5476'&''.#"'&5454676#"'6'&'&'.7676#"'6'&'2667654[W{ !GBJ3.RM M:!$/.=nU#3"^)o?VHZK" I#  U  ;%  t~  #]FWgG0ScCy.(~  Y3jZ(~ Uu>v'26CNRVZ^bfjnrvz~ "&*.26:>BFJNRVZ^bfjnrvz~ "&*.26:>BFJNRVZ^bfjnrvz~ "&*.26:>BFJNRVZ^bfjnrvz~ "&*.26:>BFJNRVZ^bfjnrvz~ "&*.26:>BFJNRVZ^bfjnrvz~ "&*.26:>BFJNRVZ^bfjnrvz~ "&*.26:>BFJNRVZ^bfjnrvz~ "&*.26:>BFJNRVZ^bfjnrvz~        " & * . 2 6 : > B F J N R V Z ^ b f j n r v z ~        " & * . 2 6 : > B F J N R V Z ^ b f j n r v z ~        " & * . 2 6 : > B F J N R V Z ^ b f j  5 T p 64>2"&&7!'7>76'#"'&''7!!7>76'#"'&''7&42"42"42&42"42"42642"42"4242"42"4242"42"42642&42"4242"42"4242&42"42642"42"4242&42"42642&42"4242&42"4242"42"42642"42"4242"42"4242"42"4242"42"42642&42"4242"42"4242"42"42642"42"42&42"42"4242&42"42&42"42"4242"42"42&42"42"4242"42"4242"42"4242"42"42&42&42"42&42"42"4242&42"4242&42"4242"42"42642"42"42642&42"42642"42"42642"42"42642"42"42642"42"4242&42"4242"42"4242"42"4242&42"42&42&42"4242"42"42642"42"42&42"42"42&42"42"42&42&42"4242&42"42&42&42"4242"42"4242&42"4242&42"4242"42"4242&42"4242"42"4242&42"4242"42"4242"42"42642"42"42642&42"42642"42"42642"42"4242"42"4242"42"4242"42"4242"42"4242"42"4242"42"4242"42"4242&42"4242"42"4242"42"4242"42"4242"42"4242&42"42642"42"42642"42"42&42&42"42&42&42"42"42"42"42&42&42"42642"42"42642&42"42642"42"42642"42"42642&42"42&42"42"4242&42"42&42"42"42&42&42"42&42"42"42642"42"42642"42"42&42"42"42&42"42"4242"42"42&42"42"42&42"42"4242&42"42&42&42"42&42&42"4242"42"4242"42"4242"42"4242"42"4242"42"4242"42"4242"42"4242"42"4242&42"4242"42"4242"42"42&42"42"42642"42"42642"42"42642"42"4242&42"4242"42"4242"42"4242&42"4242&42"4242"42"4242&42"42&42"42"42&42&42"42&42"42"42&42"42"42&42&42"42&42&42"42&42"42"42642"42"4242"42"4242"42"4242"42"4242&42"4242"42"4242&42"42642&42"42642"42"42642"42"42642&42"42642"42"42&42"42"42642"42"42&42&42"42&42"42"42642&42"4242&42"4242"42"4242"42"4242&42"4242"42"42&42&42"42&42&42"42&42"42"42642&42"4242"42"42&42&42"42&42&42"42642&42"4242&42"4242&42"42&42"42"42642"42"42642&42"42642"42"42642"42"42642&42"42242"42"42&42&42"42&42"42"42"42"42"42&42"42"4242"42"4242"42"42&42&42"4242"42"4242"42"4242"42"4242&42"42642&42"42&42"42"42&42"42"4242"42"4242"42"4242&42"4242"42"42&42&42"42642&42"42642"42"42642"42"4242"42"42642&42"42&42"42"4242&42"4242"42"4242"42"4242"42"42&42"42"42642&42"42642&42"42642"42"42642"42"42642"42"42642"42"4242"42"4242"42"4242&42"4242"42"42242"42"42642&42"4242&42"4242"42"4242&42"4242"42"4242&42"4242"42"4242"42"4242"42"4242"42"4242"42"4242"42"4242&42"4242"42"4242"42"4242&42"42"42&42"42&42"42"42&42"42"42642"42"42642&42"42&42&42"42&42&42"42&42&42"427454'&'&#"#"5&547476767636321"53&5474767676322"5454'&&'"#"0'".5632'''7327&"&5420#"&&6650&&4>332#".46546'4.>'&1&'4&&?3273'&1&'4&&?327746656627676%33272322'&&'.'4663276'&5'676332327232767#&#&'73#73#%3#53#>FuuFFuuRr ::n;/.!#K nv;/.!#K FF ]z0F yC $& 9" ^B$ 4N`P J N#u5#( #_ :%  ,+.  )14= "#$ ,-(vi'    "=  > :  H'(5 -  " +%  !E(O/D  @>87-@  MbCV    w 3 E5Hh727B  I b  -   #  $.   x   T" (  z#  )    0C2#1*V  !    O  /)0" L 0}1ӲuFFuuFFut1U>2z  1 '7 1 Go(. y  Y*  < 3 !" # 6 , )@#')H<   &  * (      }H 5/$5:@Z#  $  !B7  $  %  "   # . D7  4 (  1)"( * !9%     & 5 v        C  3 3$   "!"?   1 b$$  <<d! $$  <<d s 1  %   &   !   'Rl$.C [1vljJ7?w<W02>7676$7#"&'67>767&&54>4>7&'&'.o5) =Ei? 5 1N2( -A/EuYW<2LE|*Fv#'+463!2#!"&535#35#35#35#35#35#35#> $ $ $$ ^ߴ߳ഴೳ$ $$ $ mm>x !77!!!!KPlQ >&.6BOU2"&4266554&""&462'"2654&&"&462&"264'#54&#!5!2"&5533!!'7'7^^^ )9))cccc>YY}Y)E A..A.53##3#3P9,A[@[P8 RR`___!))9)ccccY~YY?)F).A..A]#3##3 8P[@\@8PV SR _}vA:w0327732765'1327732765'7232##%&&'45467674&&#"#"#"&74&326654.5454663232'!U' 1tv )P:U_'//'DE7D*4Q1!2:2!   vece  :O)UG!7$ "-5M)#F/)A&""   _v"2D[{670212&'.54>#"'&67'#"'&&67'#"'&&667632"'&5467674&'&54763632674'&&54632654'654'&546326545&'&54V#%-m56(+%#RP88PR, v~658)d 245 5=  "-j85+-&"RO88OS"t,/8 D26js 0**6?  ""!( JX1@%1%# @Ui.,iUB#81I,I$1%'p=$0F,  1;)  .#!' H[1J'2 " ATj-+iVB o!Reje$1IYJ ) &v/!F-!:D5 . =w!fz-Oeq3!06576673276767!#327677667327677"66367071327667476'3#"'&5476#"'&5456767632&54767063632#"'&7167#"&'45476723776767>21'0#"#"'#&747676545&'#"'.'4'1#"#"'&54767%6767654'1#"'&&54551"&'&>36766'#"&'1#"'&#&76167676676767763212676763032676767676767676'&&'454767676323275454567636767454'&'""#326767654'&#""1325>7'67667'"#32767676545&'"&"&6767>7'764 #  b&>   7  703A   IT%GQ   &D   1 , @PK5&, J ) (   P` #  -8   >& +' /   ' 4$   %     07A3TI  E%   QG   P@ ,I [ &.  }  , g #9   2 L" B5   E7  B5 #&?  3'  F2y ;8g !y ")) Sd, !  2   :"& "  #!((   ee " "=5B 7E  1@%# 5B   2F  '9  $  > .<LT\d67655>732##"554376''6554'&55467'7377"&462"&462"&4620O#  K}R>h+d ppn8h>R}K #O0x'z t%sGC;;B@;B@@#DCGslt z٫ rvv9m!5!2>5&'&'46633#"33#"567674#!5"&567674&##3201#3265&'&'4FG;<"%?,*$I*NP/!NC*$)!OBN2XC_T   ,( A,@(d6% 0/( ,"?(046vX +8%##"&547&54633#"&&##"'32##"&547M{6|#{1F /!!/ 7 _* m -hy!00!;:/`/@7'&'&7663276766.'&66''6'&%&54632''6'&'&'4763276766.%'&67276&##"'"'476767276676#"7676&&767'476767632632327667>676766'&#"01&7632%&766766767>7>72>766323'&76676"&676'&627675676'&$2( !  1   !     )  " :F.: .( p  I A-)("' &=; =@  ddmk$ 2  + ) 4z TRpm 4 B)%s  !#  ! <$   )&!! %-+ # "< , '0 "!  "#    <Fa ;CJ1%pH) .(NFL!$\V  $V[d EO :D "'= #!& 8; @N#" A@8 >$&%--@I6N 0>LXe!%26766767676'&'"#'&76767"26554&#"1312646"26554"31264^z C#kE\3&Q2<6(/PA5>&Q2<6(/PAr !> 0' & 1     1& != 0' % 2     0' &>u $4BPeu|3Za}&46%6''%"327!32654.#"!&!62"'!"&4627*#"&&54622232"&&532654&'&&#"2"&5"&4627&62"&774&"2654'6"&47&4625462#"""&463"32676654&"&463462!62"'!"&46272"&5"&4627"&47&462"&463462!#0"1!"#"&50417#"&&5#264'&'&'&54623.#"%#'#373"&5546234&"2655S5KK5%  %5K#/% :((::((:H 9+T$54K9+ F.4Kp))9)))9)KjKKjKO(:((:( _ .F +9J5.F T+9K4)9))p)9)):((::((:p))9)))9)(:((:()9))p)9))SlJ55J".  CC^B!".&C4/BB//BPB.''CC]CC]C"KjJJ5/#O)9))9)V.E .K5-F T+9J5)9))p(:((6 Z5KK5% &5KK5& :((:9))9I  a  )9+ F-5K9,S E.5J))9)p((:(e)9))9))9))p(:(( :((:9))9))9)p((:(4JJ4[ '  /CC^! 4B^}B//BB//CC/q>w&4BP414&&'&&"2>'"&&54>2%264&#"1"3264&#"1"3264&#"1"021232335#"'&'&5454'4'&'&'676765654547676325#"#"#63235"'&'&'&545454'&'&'&#"##232#"#303276767654545476 9(G'&'&'&'&&'7>77&'&'&3>&&_+(4+' $ / .,3N/MA>%96/! nvԡ 0!56G$ =.('1$*  4 v$   \PQ{ >>Ll%633276323"#56223676&547632#"'&7676767632#"#"#"'4&'45476303&56321#"'&4763256767632"#"'"#"'67232""1&546332#&54#"'#"'&'&'&'67667>766767>7673%3-      v2".466&'&#"#"'&&76767654'&#"6676&''&5476320#"'476545&&#"#176767767454&'"#"'7>36032366545&&uFFuuFFuE+.'6'   +&>*  ! !  ( O/H 3<        -FuuFFuu\69F, *    5 ((  (N][ @\tM*   &#   >3#3#%3#535#7%462#"&&>f==fff==T6L55&*&&&WWW$33H3'>tnN m}#"#"'&'&54147654547632276765054'&'&'&'&5454414763230232#''&'&##0#"1"'&'&554765454767212276765054'&'&'&'&5041&54763221232##''&'&#"1"10#"'#"1323276765045454'&'&'&'&550547632122?6574'5'&'&#""#"#"011#&'&'4'&0"#"/&''&'&'&'&''732332363232#0##"767747674'&'&/&5?232"/&''&'&'&'&''732327632333#"##0#"767747674'&'&/&5?2320'332?67767676?6541''&#"#'&''&'5767633212775&'#&#"#""##"##.'3>#.>  !#  # ! /   # f$    !    1   #  "R "   $  - ! %7 A L *    3  p L *   3 s  A  A m  3  ) L i[Jaqul][^wr" i($! * '! 1'% !%! )  & 2(' ! $ (&" *   $'!&!#&0  !)G  h:#o:  ;A!,2C :;l=   4:-1C 9;   i8% ":h  <9 C2,. =?   >l@Y&4jI0L+ >8S*B0@Y&4j@v###5!3##5!5>76735cĜ#KK}$MI$'(c1vgk)`f403v:94>#*6327#".'&6327#".'&,)E,*&XB,1N1),W,*D,*'WB,)E,*&Xt%!W)*%&!X%!%w5<r3337&>7'&''&&>75&7776&'&&'367'&54>7&54776767''&&'''&'045'&&>75&7775677377''55654'&''77&5475&5476776767767''''''667667#7>7##766  2-" 9' (4R]6 s .; J /R2h    f7=` 5r ._&**Y'@ !"(4 #zM. s 6]R4( '9 "-2 -#/>8uZ)(<#EO1\j \ > %(,4 `r!? cC# *12"+ !MaACi[ %&?;?v] B'==  < 09F_s!>(&%>0p*=1i= 6U 4R "!LG8,<2G ?!r` 4,(% > \ j\1OE#<()Zu1h>v5<Uy!!!!5#3355454'&#"#0"#"32323275#"#"'7#676325&#"#"#5#3545476302127445&&#""63232;2770#0"#"'&5477>l^P޾D6##,?? 44  % /; !  ?--Ɂ %# '&" 2 T i !)3  G  ovy '7%x J珎>E'73!73#!'#owLx=uX!bpqpuLz?!gg ?O!BXgy%8%&&7677#"765736&&54743667>77?#"&''5&547632#"'?67671&27653#"&'7265#0#"&''32463632&#"#"#"&'772654.''&&232654&''&&5466322327'&&#"#"#"&'''3212654&''&&463232&�#&#"#"#"&''32653#"#"&'96! ":    }8J:}}ihJJٕK#(W xx { \G~4g4[B::b J* ;&>< BJh UH,C8!97- =5$?),K;1& 43uDX=? SB(>L96# + W/>-#!$56H;%?F >9RB1Q YH=*D& pBC1 6/!#"#"  Y:!DMMJ st u G`F>h'G ' $PKD84'0N0>"#" 91%5,$#  9F?7A 5#)G75A   2+ 77?#"&''5&547632#"'?67671&96! ":    }8J:}}ihJJٕK#(W xx { \G~4g4[B::& pBC1 6/!#"#"  Y:!DMMJ st u G`F> ?N!BXgsET%&&7677#"765736&&54743667>77?#"&''5&547632#"'?67671&%###45637" #"3334&"'&'677327654'&'&'&'&76676&'&7&'&'&'76676'&&'&'&542767676505'&'&'"&'&'&"#"'&'&'67677327654'&'&'&'&547667632###5454703396! ":    }8J:}}ihJJٕK#(W xx { \G~4g4[B::tBt!t tbt /.  1a7   6/#/! *##$6%12+ 8 '8#"#9 11)% $ $  >0/  5 5&    40$2" tRt& pBC1 6/!#"#"  Y:!DMMJ st u G`F>J10@ EI;5   05# 9    "+ )D2$%    ( )=7   '  $' &   $ !/)$J$>`8p.##3'53#"#7!!53#5!#&&##32653#4&##32673"#53'323&#"5327677'#5327#7&#"#5327#"#"3"b$C``C$ % // $0EE7)=%PO 40 Zc+ ;<%BA 40  Kp+   iii/'u{/6ojw\We>g>]!"&5463!2675676733#3232767##"#"'&5475#45476322320#"##"'&54- NN   / $0K&    vW#'!&&54673!#5!5!5!%5!5! 4Z=7g1c  Y0Tug666666*66?w'+/3$"&462&"264"&462"&462"&462'7'7'7ԕԕZ@@Z@MmMMmLmMMmLmMMm U:UU>ԖAZ@@ZYmMMmMlMMlMOmLLmMfU?U> > #)''775!!!%!3#75#3#75##%5.=>w NVW+W-D;==++'q+WW+WW,>v###5'3#3#!#53#53ǜmYYlYYKf(faWbab>v 9_mz1B[lz#'2%3#441&'&'17#46767&''367%6767632163#323632#&#"#&#"#"#"#'676323&'671&7656767"'&'>7>766733%4541>7&'&7&&77&#&54537#0747'"#"'27632&'&'&'&&#"&'6333&#.'454567676'5676727#&'&'>7&'&'4'732363223267"'#766#4456727.'&'73745&5456&74&g CBQ6`kCxZ"JYN@b 22TS ;;    GG  X!;;x+* ./ S/LIUg  *6 O" '" 1)..L9 dNT;C'RF*+ F, ZZb_ 0-jOZZ0  dhN _<  QBt.#Am[ "3noL>2 & HH AM  >R/. & 3<T "" on#&|9K~ 3~Y . $ 5DDM5$ DE YY DC7k YYff7eeI,.K k 21#) #^M *|a9 eh{^Bjffc  $ K  pX 12e  Y% ?<F$% `DD IJ RH3.>6LRk-ee/2I#HUM-<@JI` %$F4>z&8`{ 1\gt ,:4v{"&1"##"'677#"'#0"#"&'&''7&'#1"'&'&'&767'767''&#'&'&'&'&'&'&5454765677676767632""##6671''67767632632;2326654&#0%''&'&'367&654'&&"3266'4#0"254#"25&'2327676'&''"##327%0&1'&''327%"''&"&'&'67667232&'327675'&'673232367&'4'4'&'&'&'&'&'&&'67673'67&'&'&&'76776?567676765054''1&'676724'&545&'%67676&545!6766454''2632436747677654'##"&''5&54767732767#"#32367&'#"'"'4'#"'23276767"1"/%67##'676767'&#"676767''&#&""''7673203721657673032776577&'67%67%&'67''"&#">7672"#767#"#"'&'#'0&&''&#'&'"0'"'0&'3272767676767676767>77102;276767676767676735#353#35#53&'6767663'673? 6767""#"67767###1##"0'&'&'&''3212776?6767676?'""#"'&'&'3#&5&'';277367736767677664"&#"&&5&>366%254"3+&#"67&5&632%454''14'65450467&'&'341&'&5451676763&'&'&'&'&'&#""#377636775&5476747704147733423031045470624367476735654'&'7533063303054'3%#335#353#35#7#5#3#535##53     ( #, 5.31. !:#2  3-,!- &[[ts[['  . 06$>>   .97O  )       )(  Q   MO/&   $  ON8     $lm#       #  (YYYY(  ,        ]Y! ; !Y .42/   y / .+ '+ PM  NP()=B0.AGG  I 23  QT)$8-Z!MA)!CN Q>m ,  )'#%   '7        ,=+ AM73NC  nj{      %#      7 2 * lE       dY&&o      Y    ?WUB      -$O$ .h-  O#-  ,,=P5 \ 5    "( F'   5 ,3(! &&13*    mGGGGm    f Z l $z  U\ Zc  2   U   Y\Y  4,#    A  cc   ""&    # "# E  N  e    ;: 2,+c2279^     ( "   Kh2\Y)3tm#"ZZ#^A2  #/  +0F\    /     3++/   + \J&     6 /    5          ! L??H PF79<: ;C H>>M ++^d99dK]#IX&'&'&#"'&'&'67677327654'&'&'&'&547667632###5454763&&''"/&547'"&&''&46675&''77>37&4667763>26''3276654&"( 7#  <; B)B/     A<-  >*fk# &  !  #&  !  $4% &   '%,%/ 0   ,):3-e,  # #   $#G $$ >GV0&&'&'&1#"'&'&'67677327654'&'&'&'&547667632###5454763265&'&'4633#"33#"&567674#"##53654'&5475&547674&&##5323''''77777777''''' <).-  23$    2-#0! nNnI'@6"<<@6@BjB@6A:: 7@'%&%%&2&%%&%%&%%&%&%&2&%&%&%%& %"$%   ! -'#9" ( *)()A@6+*#"%&VY&%#$U6@G((('II('()F@6T7%%YW%%##$6?A))(* (+++++;++++++++++++++:++++++++tL12232'&'7676767676545454'&'&'&'&36&'&54545476?37676'&#"''&'&545454767667217767654'&/#'&5454547732#""#"'\V]E V =z _X^=* ^]""  oo C 6Def     F}! JD!TV '*S9C+,$$b[b  $        I   &Q#=[}%#"'&'5#"&&'&54763!32###"33332655326554&#4&#!"3332332326753265#"1#"1"'&'5#"&547633!#'##"1"'&55#"&&'&54763!2###""33332655326554&#4&#!"333232326753265#0##1"'&55#"&54763!#5## 0! #-" #> #ed.``!!4&6!-&6!Bt" 1" #." #>"de.aa!!5&6!.&6!CQ( % %(c)&7&$6'#&0( % %(c)&7&$6'#&Dv3IQY&>76&'&''&#&54767>".'&6!654'&'.4&"264&"2  ,{Z-3;"vC+hSc&)   )( XX2͟: D4+& . . . .uʁ?K<4mk-*2U* D8 >LLe 6^o01zf, - - - - >vv~463!2#!"&5%30323276767654'&'&'&'&'&'&'&55476767632125&'&#"#"#0#"1#"#"'&35!33>   !!        ,    }|c  `      Z(#    OOZ"h$JY&&'&'&1#"'&'&'67677327654'&'&'&'&547667632###5&54763) 8#;< B(C/ %   A<-  >+f &   '&,$0 0   ,*:3-d,>vv~463!2#!"&5%30323276767654'&'&'&'&'&'&'&55476767632125&'&#"#"#0#"1#"#"'&35!33>   !!        ,    }|c  `      Z(#    OO]"h#IX&'&'&#"'&'&'67677327654'&'&'&'&547667632###5454763( 7#  <; B)B/     A<-  >*f &   '%,%/ 0   ,):3-e,Dv2%#0"1".546732672#"&&54663643+!n%Ik65JG8_S/"/6,dA",I\f_T! G-$Jіv\ ):dC@ wLj%#1""#"'&'"#&&'4675476730616726533'654'4'&'&'670616767632>7&'"&'663267&&467&'"&5567&#"3327263672663676636726767676767676%2"&546126654&"8(      yC:1:7N7:1R 5[5 <0%&-C^B-mkKT7$--$6&/2LC`    k! .   W:/  |M/&'77'y&Cf    U  '   5Y4$0; ).BB.\qJg?$+ ?O? D2CZZC4;B;^C    X  Cv.6:>Qaejnsw{'7''5'75'5''55''7%7'57575'7'5'75'7'5'7''77'5557'5'#7'77575''6777'757'7''84447-^8&llC)O)C57lm6ll7m77777E6=.(66558&7l6(K 766666l77m767677l77755767665kl66665644l665CB741ml5555n77767 > 8;N)>~?@Ju]^?]J@? ?>>i?|>=>>^? _? L#= >;N)? |>?^}=#,P!> >>}? ? ? |!A _?  W}>? ?>>> ^(&?>}?~<<? >av8o'0'&&'&547676323767730#4#54'&5''&'&54767632376767730367#'&5476'''"#"&'&545454767> ' 7,-/g&'7!-V !& 5Z 0i'(?BB!.gYQǁM[D+!BP"/%&f-$ 9+   (?/2  )( H7~#N6ACmj\|-;NSc/Y![{ $n4H'=-1  S J:$WR`iU^ cw_nlRF#{5E !40A*xq`v8o'0'&&'&547676323767730#4#54'&5''&'&54767632376767730367#'&5476'''"#"'&545454767> ' 6,-.h&&6!-U  & 4\ 1i'(A>B!/j]L~MY@*!BO" ,&$e,#9  (?.3  )( H7}$N8ABli[|-;MRc/Z!Z{ #o*N &=,1  T K:$XMZcR_ gv`n lQD"{4E   41A*   Lo[nzr#""#"'&&547&546766323232#"#"#"#"'&'##"#"'&5454547636327332676545454&&'&#""#"32327.#"#"036767_hr6=?87T h/4I!\3D(?*,EEQH/ KA %@3&WtG/d3-`VPj '9--[1!* A)e2L U;:A^,uB rY%* &.2CmVV7*"$#HY %D:M93l @q*U! FDRq$*:$>8  #'+/37;?CGKOSW[_cgkosw{''55'757'75%7'7%7'''55'757'75%7'7%7'''55'757'75%7'7%7'''55'757'75%7'7%7'GcKgU[eb[0U~PLMN T>HKTN^QZPHNU[eb\0VPLMN T>HEMO^JSPHG=V\ea\0U~PLNN T=~H~DMN^KTPHG`V\ea\0U~PLNN T=~H~KSO]RZPHMnzr#""#"'&&547&546766323232#"#"#"#"'&'##"#"'&5454547636327332676545454&&'&#""#"32327.#"#"036767_hr6=?87T h/4I!\3D(?*,EEQH/ KA %@3&WtG/d3-`VPj '9--[1!* A)e2L U;:A^,uB rY%* &.2CmVV7*"$#HY %D:M93l @q*U! FDRq$*: D7 9@Zep'77'7'#373#003#!3101#73665454&'#3665454&'#x34oocooѮB#  #-577mmnm, $- 7m!!&I~~dccLLdcVV 9$+)$ %++'#b&! '1 >-+ (43#!3001#3664&'#32>54&&'#II:!/:!&G$$+  ++Z0"#)1"&!> 6 +    D8 LXd'77'7'"#"'&54545476323232'&#""1"#"32323277#"%#5#53533##5#53533##"#"#"'&54545476323232&#""1&#"32323277533##5#53533##5#5x34oocooUaB?EGh63G $G9@  7777667704M4399S-+'.2!! /1*77777777777&I~~dccLLdcFH`hMK -! - 7777777s7;MU@<X%&41&#667777667777Zv$+$"&462"26475!5!5%###'333%7!!hؙؙvvus-?$= ?=$6u4,ؙ4uuu?$<t-v3[?=$/g +6FTa#33###&&'5##&&5455#1#"&'55&'&&'##&&'567632;5##"&'5667335#"1#"'&'56673367667556673235546673355667323032#"1##33#"130#54673#5466730#5466663:3#"1"'&'%3212#"##663:3#"&'%32#"1#46323223#"&'56%32#0#3#&&5<73#&'&505453##"&#"'!"#"'&'&54545476672232327232363223##"#""767747674'&'&''&5773232#"'&'&'&'&'&m.C  @2-  RR "K( )9 .CA0n A1 * R  R $o n0A  8 8  8  "+ e  e    e "+ h h   e&  H h h  h8  8  8  %%ML @    9  y(  b- -r R   -l p."!mn@2+ SR   2@ >20A  B/)HA1 " R  44h    h  hh  8   8   *    le   + h   36hby%   ,EI=/4F  >v&-!!"3!2654&#463!2#!"&&5##3>l5    ~}вi    p fv7Lc=Xr63061!3#"#"#"#""#"#"##"#7&'0#"#"#""#"##04547633265054'&#"1#"303326705&�#"#":323276554&'*#"#"03323276554&'*#"#"33266554&�#"#"!3:3267<54&#*#"#"%677&32333267054'233670323230332675454'&'2330#"'!#"'&&'&5054403326654154'&#"1#"13227655454'&##"#"136654554'&#"1##"332650554'&#"#""_*2cd0. ) $x   !     v     (  w      abc^0 % bb  $ 2 "8 ($z    + #z  %  " ;!  *t:;     Gs$  % &   x#  %  % $ rvv!5!%5'7575'FG;<⠠82XC_T]]U68U]]D:r "*.29=AHLPTi"-8@HSX]f}If  % 355""#3'3#44&45#'52623##73 #  57!#'#'57!#7%#'77"&#7&'77!''4'7''577''75%7'!%'775%67&&''576673566%#5''623766737'3'37'3"&""75##>75375%7'!%775%67&&'%#53'33'3'5##263#5''62376675#'57!#'#'57!#'>7465#'77''45#'77"&"77''37''73#'77''7?'7#733733733#7#3#7#3'#73773#67"#3#667""#3#6#7326367#7'''"32637#73263>73#7#3#7#3#7#73373373#67"#3#667"#3#67#737737#7337#7337#7'''#3'3#7#17373#67737#7'#3#7#733'#733#637#667#37#'773#'777'7GOLTjAKY\ m~#{}V su#L{xmw!\ 'L$Y"^z  (     = &1;U 2 2 / 0"50 gZ&  8M8m I5pG%;:$1&,  3 ,  2"!!"!" #00 /1   )4l  Y553 3 775P ; @'j)j)0M9>c c  cQ %C > P  $Q  Q f,G*>#F)>#F5A5@ 2@2@?33  N!7 N!7N*78C6j5kf-c`q4m  IG5J5B # ?$N $ QQ # # ! ! % %0GTL BJYv# {}U  su#Lae {xn5FFrXFFDDDDu>mIIII+40+2 P ,7 l _dG,$51,,,=K#$+ GP +48= +18   # 452]7{|#^1}8!#$+ ,$$ |k # # #"""!$ $! ~;w!!D %  &   & 543688ȟ5#~3|H4|I3." Z,7D&',S&',OB,-|%i8B`|j|j D71> SI OO  d-c`cSPv45,,,OP+O: $ P: j OVjJ # " "&  & >|+#0#"&547677232%#"1#&'&54632       s    0  v37yV_*r/>w %'7'7nYYfآa[L[rDv '77'x34ooc&I~~dccL>y)A632'#"''&'&547#"'&'&5477632#"'2654&"632} cp  0 `  1  -     f`f_ +  =   + w  W"">y3K&&#"#"''&'&547632#"'&5476776323267#"'2654&"632 :  0 N  0       " +  W  + "f  W"">Vt!#3!5!#535##5!!%53!53&&'#/&547'&&''&46675&''77>7&&667763>26'%3276654&">i>}xiA@ "  F.-  = -.A "F.- #> -/!, )DaD, ^i}>/niW/. $# C: B%($  B !; A":$0DD0>v!#3!5!#535##5!!%53!53>i>}xi, ^i}>/ni>v i~3!7!33373&&'#/&547'"&&''&6675&''77>7&667763>26''3276654'.#"JJOge JgeN+ /), / (<9 ,'+  ,,X ' (> 3!7!33373JJOge Jge<9>v!!!>,>n "&!!!2653733#'##'!37333#WPQ:S;B;-*9J9-+60'`J:h<[8Ni*::*.5EE5Lq1Z&>Tk2>4."&473%&'&'#5&'&'>7677676&&'676673326%&'&'>3&&##"762"''&54154666'&7&"2776554'1GyyGGyy       "H1䎎1Y$ T< ;,"H |^1Y$ T< ;,,,,Qw33xPQw33xOOO}II}}II}!CC!   BC!    !!Na% SWW!NULa% SWW3 4  4/OSSOOSS/ ^ // ^ yvo %'5% %zz{{{zKmmmL 159=IZju!2#!"&546!2#!"&5463!2654&#!"753'353#!2#!"543!2654&#!"7!2#!"&546#"&4624&&#"26&4&"32!"#" #"3!27654'&#462"$:%$  : R,'77':'7 g”ff1:qE11EE1: 6 v%$:%$,(88P8q8`9V{{{9Y~Y&.?iq;q,(>=>^^^& $$$    +7'&77'7 g-g 0EE10E7 >$ $$$,9O99(9`8zzzYY@/%)A>1-&&,,&I^^_26:>BFNV#""1#532327676745545&'&'&'&5414767632125!5353753533#37#535#5   5- _WVV ; tAABB_AA_BB_AA@FAA@AvH&*.26.'!!>767%#5!5!5!53T O\X '&704)!  bDh,?G}}" 1aE6 "#//8"g<(Q)2B66`77_7777>`0Hz%''7'7'7'77'#0"1"#"'6767&'&54&'&'676545677&&>77"&''>76677.'&'&'&&''>7654'2>77````182* +5-+*/7%6! "#!!g ,lIb 5o39  j..,O^> !J 4  CN-%!  0Q)LL))LL)" 7%$11#*2"  "k&*( -)40=% "]&&;/, -[5N ND"$+)) '$-@><h2 632""#"##%%!!!!!!!!!!!!!!.'>722267>7&'"C$$   L%H %    ' O ete& PPOmF'2(1(2(1(2(O|DI?M1"8)5&*br#"Vs > *6@J[}2%5463%5!%"&55!#2##2##32##32###!2#!"&'>&''#37665337##&''##4{%~  Ϧu ~#/97$'69.!(3'ee==))N O wwt   ve^2#&'&667>&'&'">1#"#&&'&672326656'&'.7&>6T>BL.3-O  'VWF,u'N7:%35&*: ">+'A"m\>b{4Xy :&&5&##""P f]Ee>>ij6676676&&7676654.#6766726#"'&&'&&'&'&5476676676&77>76766&'&5476676&767223&'.#"#.'&5476323>*$! ?'( GD !2    ##    +9O-,# *LEX! !      2   @;5"6   RAI  /4\?)&+V" +,+v$>E.@$53#"v;)TP>' C Ci8;C>#+ $   .I#@ &1 ?#'8 6&  AG74eB $ #0P`1UJ& 1"4B\do&'"#"67654'&&7>76324&'"#"0323267301264&#&'#"'&&67632264&"6"1264yXOO  OI: &1>#15K.,i#('&fK77?ER@o(0H SJl 1C3P"=0$ @88'5  G3|),4.:J_> +  1 2A[cl&'"#"67654'&&76676324&#"#"1323267"301264&#&'#"'&&67632264&"6"264yXOO  OI:gF15K.,  i#('&fK77?ER@o(0H SIl 1B4OEe@89&4    G3|*,4.:K_> * "<7U(L\nEu0C]s&'&7676545&'&'&#"6767632327632'&'&'&"#"&545676766#"&767667>767611#7676721'.'&'.'&'&%"'򠖈'.'&'&&'&&'47232&'&'&5476320&'&&'&#"'1'&'&767647667637276&'6654'&&5476&5&'&&'&'0'6746762&'&'&7676&7674&'&"&767#'&'&766765454'&'&#"06767632327632'&'&'&#""#"&545676766#"&7667667>761#0767676#'&'&&'&''&%#0'6367>76'&&'&'.'&'472&'&'&547632&&'&&'&#"''&'&7676'767667&47667637276667&'�#'#&'6"67676654'&#"4547632"&5&'&&'&'0'6767623&72167&''&'&7>767667654'&'&#"&76674&'&"&763%'&'&767667676767032'"&'&'467667"67632'& !%,A=     ! HG  $$  9ro2(1!3 78Yw&  LFHDE  -+! #"  QR  W #W3RR  &/0 #P e  # i%  ( )( #2   =   * "%-A=        GH$$  un@A1< 7Y8YZ"  LbbEE  Y"#;0   QR  #) & Z8M! O  '0/# 8''  "&! e#~: _  # 63B%( 2#-   .    > "   * "    '' = | (" v   r Q')$  . OR    +0 ]   ,!$0. !    !# 9Z ^ &&6on, bc  g ) i  =cg uu n ) - 0 ,  1   ?   e I') $ )   OS    X  ]     $!2,    = b0b `  Fo5  5 bc  *1. l  D\( g tv  _`  AV 0 l) =$   . f 0 -   ,= '8!$m 2    c   ,!)  `_* (- + d Fv?Hr|2##"''&##"##"50517633'"57'547632##"556.#"#!#5353325'"4776##""''4##"103327742#5!!!"5543325'4##"332=4332554#"554##"332574#e4`/[I&4.(43 8r %''&&&)%{ToL5W"0C"XQ^^<‘*/ L(NXT66YS7.% @E>| '*6BNRVZjz'7'77''7'77'"15#"547777'7632'7632'7654''&#"'7'77''"'&47762'&47762"27764'7632'&'&&'&'&54"'&'&'&'&'&5477632'"654'&'&#wXXbaON8YXbbOOXndi[% P7% VYVY_a`MPNH K  ! LK& H|  GH3  ; -0 3  y 2+;#/ \XWbanONXYbbOOWjdn[#3 + 2K:> /8Kc227>723!!&&'#67&&703021""#6773#6673#3""#""666673&"#223""#&o . H H % Za $T[ &?ez?>_]X=  )  9 B 7 '( O E+ *aDz!cV !!"%+')!&J>v%-64>2"&&'6632&54147%%%#"&>FuuFFuu[6cG0aJU3nӲuFFuuFFu7N[2""{e7`d!^o>(0 %7'7'77'~EE22EE>v0 PT%7'7'77'7676763236321#545476776767454'�#"#454#5~EE22  !7.%5 t*  xxEE!!12 (     __>(0 %7'7'77'~EE22EEcv"#"'&#"'&'&'&'&'"&&'&'&547654'&'&'&5476'&#"#&'&6767656'&'&547676763226767636232127676545&'&'&'&&'626654&"3274&&'&#"'621.264&"367&#"32'>.{)34          2/(  ()+57C36F9 ! 'NN  4, '78^U, -  3(   0!E540,.  $B \ ))= &!#Y  FW      ZM  %2$A&+ *9 "   4%%-& +7)x  C    "T"'WD !!   ">!3N^3>7632323"#667.%3"#5#*#67021673*#45&&'#&%3213"#4''''77777777'''''>GV  9 ?N C91l w,I'(, 7;,vji44544"&H44544445454454H&"45445454V   N  l vvH('vvJK 7;}vv``v+++++:++++++++++++++:++++++++> .GX3>7632323"#667&&%3"#5#0#6703673#45&&'05&%3213"#4>G++  9?NT>91l Mw,I'(,, 7;,v ji++  NTl vvH('vvJK 8;}vv`` vv+430#"1#0#"&5##"##&'&47&&5474>3#"3  E N ;G+HX1+=<6     "xGKvG%ABQTJS?v5Og%4>326654'&#"&&.54>73#""&&54>32>54'&#"'&&54>326654'&'#.54>32>54'7'32665#4&'#"6264&"462".AE&2Y0XtDfkWYmgEuY1I(:F2"UX.WsD˙MK&7J7&G/tuWX)IS12Y0hcN>T &7J7&G/jdFtttɗ1I(:F2" Q9''''L2O2 C`,REtXnx "t yoYuES I@) .Sns"ZDsW%o4;W/(>FQRvt"t9>[1C`,RxNJ#;W/(>FQzmYuE suR I?* .S7"GK''h'']vo%"'&5467&'.&'&677#"'&54767&547676.7>7676322>6622>767632rD8Va ee6   ($ *$?   1I  ;  $32!!!'!$462"7"&4632'"26554&#"&462'32#"&54667'''''77775'''7'77>#"'.#"'&66762!4>32!!!'!$462"7"&4632'"26554&#"&462'32#"&54667'''''77775'''7'77>#"'.#"'&66762#58HqD8dF:$UH9Q99Q(#22#'',,=,,3$$3$>  #!!#   """",#'6:7*$GT9i9TG1yC*C0:h58HqD8dF:$UH9Q99Q(#22#'',,=,,3$$3$>  #!!#   """",#'6:7*$GT9i9TG1yC*C0:h2 !%!" JJ0Q99Q9 2G2'',=,,,$3$$P  !#""  " "#! &+( -N^-X] %^N2 !%!" JJ0Q99Q9 2G2'',=,,,$3$$P  !#""  " "#! &+( -N^-X] %^N>!> N73#3#%3#535#73!#5!cioXoXcvnYttt.tit9tEvHmp*/6;AEKPV]!6323##23222'"#2167633"'&'#"###"'&''#"1"'&55676323032767677""#"'"&#"#"'&'&'&'&'&54766326332676763632#"#"''&&#%*.677676323##&'#/5'57''7557'=7575753373777333'#'#'+'''#'''+''+''5;773777337;77377;7373""#"4#''+#'+&&#&"#/"/&'5&55675?615776776767;73;7;7362323'#"&###"#'#""#"#"##&#"#"+=733733770#"1'+'#'#"'"'&'&/&'&'&'"#"##'#'#+'###&#"#76776677633677373;7377232733377373'##''+'''##'##'#5'7733223327777777773337#"#""#*#""'+#"'""#""#*+"'""#"1*#"#"#"&#"#"#""&#"#"'#*#""##""'+''&''5&'&/&''&'766372323232322;232303323366326232;6323223232327233730372767767707677>76?>76?23236;22323232;7223323:36323232725333331/#55''75'75'755'7557575757535373!%&7767763%777%'7!77'3'77%737'7'57%773##'#''''+'+5'7353773333573733'57%77772321#"#"'&''654576772#"#"'&'7&5457677632'#"'&'5454576327767745455&'&#'3#)   =   A [ d    d '"%RN*  `7 = +  y        I  $    # $ %        ;4               os<{.w0 0>~Aooop#&#jl l  jk l m k !    D       E$   =r5  )  %=U\ZESLm l0' #k    \;-. /:l7s_^%_^      R va "&*.26:>BFJNRVZu!!35'53'53'53'53'53'53'53'53'5353'53'53'53'535353'53'53'53530##"&5414633#3'#5226654&"537'&&##5###"&'654'1&546332a&HIIkkkkkkkkkkkkkkkkklllllllll?klllllllll L F +  &A 4  .! !/AIliI&HHHHHGHHHHHGHHHy J NN  +__H m 11 l! !//;9B{ : <  r <  <Copyright (c) 1980, AnonymousBroot Icons Visual Studio CodeRegularFontForge 2.0 : Broot Icons Visual Studio Code : 1-1-1980Version 001.000Copyright (c) 1980, AnonymousBroot Icons Visual Studio CodeRegularFontForge 2.0 : Broot Icons Visual Studio Code : 1-1-1980Version 001.0002      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~glyph1glyph2glyph0glyph3glyph4glyph5glyph6glyph7glyph8glyph9glyph10glyph11glyph12glyph13glyph14glyph15glyph16glyph17glyph18glyph19glyph20glyph21glyph22glyph23glyph24glyph25glyph26glyph27glyph28glyph29glyph30glyph31glyph32glyph33glyph34glyph35glyph36glyph37glyph38glyph39glyph40glyph41glyph42glyph43glyph44glyph45glyph46glyph47glyph48glyph49glyph50glyph51glyph52glyph53glyph54glyph55glyph56glyph57glyph58glyph59glyph60glyph61glyph62glyph63glyph64glyph65glyph66glyph67glyph68glyph69glyph70glyph71glyph72glyph73glyph74glyph75glyph76glyph77glyph78glyph79glyph80glyph81glyph82glyph83glyph84glyph85glyph86glyph87glyph88glyph89glyph90glyph91glyph92glyph93glyph94glyph95glyph96glyph97glyph98glyph99glyph100glyph101glyph102glyph103glyph104glyph105glyph106glyph107glyph108glyph109glyph110glyph111glyph112glyph113glyph114glyph115glyph116glyph117glyph118glyph119glyph120glyph121glyph122glyph123glyph124glyph125glyph126glyph127glyph128glyph129glyph130glyph131glyph132glyph133glyph134glyph135glyph136glyph137glyph138glyph139glyph140glyph141glyph142glyph143glyph144glyph145glyph146glyph147glyph148glyph149glyph150glyph151glyph152glyph153glyph154glyph155glyph156glyph157glyph158glyph159glyph160glyph161glyph162glyph163glyph164glyph165glyph166glyph167glyph168glyph169glyph170glyph171glyph172glyph173glyph174glyph175glyph176glyph177glyph178glyph179glyph180glyph181glyph182glyph183glyph184glyph185glyph186glyph187glyph188glyph189glyph190glyph191glyph192glyph193glyph194glyph195glyph196glyph197glyph198glyph199glyph200glyph201glyph202glyph203glyph204glyph205glyph206glyph207glyph208glyph209glyph210glyph211glyph212glyph213glyph214glyph215glyph216glyph217glyph218glyph219glyph220glyph221glyph222glyph223glyph224glyph225glyph226glyph227glyph228glyph229glyph230glyph231glyph232glyph233glyph234glyph235glyph236glyph237glyph238glyph239glyph240glyph241glyph242glyph243glyph244glyph245glyph246glyph247glyph248glyph249glyph250glyph251glyph252glyph253glyph254glyph255glyph256glyph257glyph258glyph259glyph260glyph261glyph262glyph263glyph264glyph265glyph266glyph267glyph268glyph269glyph270glyph271glyph272glyph273glyph274glyph275glyph276glyph277glyph278glyph279glyph280glyph281glyph282glyph283glyph284glyph285glyph286glyph287glyph288glyph289glyph290glyph291glyph292glyph293glyph294glyph295glyph296glyph297glyph298glyph299glyph300glyph301glyph302glyph303glyph304glyph305glyph306glyph307glyph308glyph309glyph310glyph311glyph312glyph313glyph314glyph315glyph316glyph317glyph318glyph319glyph320glyph321glyph322glyph323glyph324glyph325glyph326glyph327glyph328glyph329glyph330glyph331glyph332glyph333glyph334glyph335glyph336glyph337glyph338glyph339glyph340glyph341glyph342glyph343glyph344glyph345glyph346glyph347glyph348glyph349glyph350glyph351glyph352glyph353glyph354glyph355glyph356glyph357glyph358glyph359glyph360glyph361glyph362glyph363glyph364glyph365glyph366glyph367glyph368glyph369glyph370glyph371glyph372glyph373glyph374glyph375glyph376glyph377glyph378glyph379glyph380glyph381glyph382glyph383glyph384glyph385glyph386glyph387glyph388glyph389glyph390glyph391glyph392glyph393glyph394glyph395glyph396glyph397glyph398glyph399glyph400glyph401glyph402glyph403glyph404glyph405glyph406glyph407glyph408glyph409glyph410glyph411glyph412glyph413glyph414glyph415glyph416glyph417glyph418glyph419glyph420glyph421glyph422glyph423glyph424glyph425glyph426glyph427glyph428glyph429glyph430glyph431glyph432glyph433glyph434glyph435glyph436glyph437glyph438glyph439glyph440glyph441glyph442glyph443glyph444glyph445glyph446glyph447glyph448glyph449glyph450glyph451glyph452glyph453glyph454glyph455glyph456glyph457glyph458glyph459glyph460glyph461glyph462glyph463glyph464glyph465glyph466glyph467glyph468glyph469glyph470glyph471glyph472glyph473glyph474glyph475glyph476glyph477glyph478glyph479glyph480glyph481glyph482glyph483glyph484glyph485glyph486glyph487glyph488glyph489glyph490glyph491glyph492glyph493glyph494glyph495glyph496glyph497glyph498glyph499glyph500glyph501glyph502glyph503glyph504glyph505glyph506glyph507glyph508glyph509glyph510glyph511glyph512glyph513glyph514glyph515glyph516glyph517glyph518glyph519glyph520glyph521glyph522glyph523glyph524glyph525glyph526glyph527glyph528glyph529glyph530glyph531glyph532glyph533glyph534glyph535glyph536glyph537glyph538glyph539glyph540glyph541glyph542glyph543glyph544glyph545glyph546glyph547glyph548glyph549glyph550glyph551glyph552glyph553glyph554glyph555glyph556glyph557glyph558glyph559glyph560glyph561glyph562glyph563glyph564glyph565glyph566glyph567glyph568glyph569glyph570glyph571glyph572glyph573glyph574glyph575glyph576glyph577glyph578glyph579glyph580glyph581glyph582glyph583glyph584glyph585glyph586glyph587glyph588glyph589glyph590glyph591glyph592glyph593glyph594glyph595glyph596glyph597glyph598glyph599glyph600glyph601glyph602glyph603glyph604glyph605glyph606glyph607glyph608glyph609glyph610glyph611glyph612glyph613glyph614glyph615glyph616glyph617glyph618glyph619glyph620glyph621glyph622glyph623glyph624glyph625glyph626glyph627glyph628glyph629glyph630glyph631glyph632glyph633glyph634glyph635glyph636glyph637glyph638glyph639glyph640glyph641glyph642glyph643glyph644glyph645glyph646glyph647glyph648glyph649glyph650glyph651glyph652glyph653glyph654glyph655glyph656glyph657glyph658glyph659glyph660glyph661glyph662glyph663glyph664glyph665glyph666glyph667glyph668glyph669glyph670glyph671glyph672glyph673glyph674glyph675glyph676glyph677glyph678glyph679glyph680glyph681glyph682glyph683glyph684glyph685glyph686glyph687glyph688glyph689glyph690glyph691glyph692glyph693glyph694glyph695glyph696glyph697glyph698glyph699glyph700glyph701glyph702glyph703glyph704glyph705glyph706glyph707glyph708glyph709glyph710glyph711glyph712glyph713glyph714glyph715glyph716glyph717glyph718glyph719glyph720glyph721glyph722glyph723glyph724glyph725glyph726glyph727glyph728glyph729glyph730glyph731glyph732glyph733glyph734glyph735glyph736glyph737glyph738glyph739glyph740glyph741glyph742glyph743glyph744glyph745glyph746glyph747glyph748glyph749glyph750glyph751glyph752glyph753glyph754glyph755glyph756glyph757glyph758glyph759glyph760glyph761glyph762glyph763glyph764glyph765glyph766glyph767glyph768glyph769glyph770glyph771broot-1.46.3/resources/syntect/README.md000064400000000000000000000014041046102023000160250ustar 00000000000000 Broot uses [syntect](https://crates.io/crates/syntect) for syntax highlighting in text files. This (excellent) library needs Sublime Text syntax definitions for all languages (when a language definition isn't found, Broot displays the text monochrome). Syntect doesn't come with an extensive set of definitions. The [bat](https://github.com/sharkdp/bat) project maintains with care an important list of such definitions (most as submodules, some with patches). It's the best public list I found, so I've included the resulting syntax set here as `syntaxes.bin`. 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. broot-1.46.3/resources/syntect/syntaxes.bin000064400000000000000000033741451046102023000171400ustar 00000000000000 Plain Texttxt text.plainYxڅA 0 + >/<*9|efIƸy=->+؂\m (vp{Ц}Mgq/a SCASPasa source.aspapostrophe_comment_begin'asp_builtin_classesB\b(?i:Application|ObjectContext|Request|Response|Server|Session)\basp_builtin_eventss\b(?i:Application_OnEnd|Application_OnStart|OnTransactionAbort|OnTransactionCommit|Session_OnEnd|Session_OnStart)\bclass_magic_funcs)\b(?i:Class_Initialize|Class_Terminate)\bcomparison_operators[=><] constants\b(?i:vbTrue|vbFalse|vbCr|vbCrLf|vbFormFeed|vbLf|vbNewLine|vbNullChar|vbNullString|vbTab|vbVerticalTab|vbBinaryCompare|vbTextCompare|vbSunday|vbMonday|vbTuesday|vbWednesday|vbThursday|vbFriday|vbSaturday|vbUseSystemDayOfWeek|vbFirstJan1|vbFirstFourDays|vbFirstFullWeek|vbGeneralDate|vbLongDate|vbShortDate|vbLongTime|vbShortTime|vbObjectError|vbEmpty|vbNull|vbInteger|vbLong|vbSingle|vbDouble|vbCurrency|vbDate|vbString|vbObject|vbError|vbBoolean|vbVariant|vbDataObject|vbDecimal|vbByte|vbArray|vbOkCancel|vbOkOnly|vbYesNo|vbYesNoCancel|vbAbortRetryIgnore|vbRetryCancel|vbYes|vbNo|vbAbort|vbCancel|vbIgnore|vbRetry|vbCritical|vbExclamation|vbInformation|vbQuestion|vbDefaultButton[123])\b functions"\b(?i:Abs|Array|Add|Asc|Atn|CBool|CByte|CCur|CDate|CDbl|Chr|CInt|CLng|Conversions|Cos|CreateObject|CSng|CStr|Date|DateAdd|DateDiff|DatePart|DateSerial|DateValue|Day|Derived|Math|Escape|Eval|Exists|Exp|Filter|FormatCurrency|FormatDateTime|FormatNumber|FormatPercent|GetLocale|GetObject|GetRef|Hex|Hour|InputBox|InStr|InStrRev|Int|Fix|IsArray|IsDate|IsEmpty|IsNull|IsNumeric|IsObject|Item|Items|Join|Keys|LBound|LCase|Left|Len|LoadPicture|Log|LTrim|RTrim|Trim|Maths|Mid|Minute|Month|MonthName|MsgBox|Now|Oct|Remove|RemoveAll|Replace|RGB|Right|Rnd|Round|ScriptEngine|ScriptEngineBuildVersion|ScriptEngineMajorVersion|ScriptEngineMinorVersion|Second|SetLocale|Sgn|Sin|Space|Split|Sqr|StrComp|String|StrReverse|Tan|Time|TimeSerial|TimeValue|TypeName|UBound|UCase|Unescape|VarType|Weekday|WeekdayName|Year)\b identifier/[a-zA-Z]\w*|\[(?:(?!%>|\]).)*(?:\]|(\n|(?=%>)))keywords&\b(?i:Empty|False|Nothing|Null|True)\bliteral_number_decimal`(?:(?:\d+\.\d*|\.?\d+)(?i:e[+-]?\d+)?)(?={{whitespace_or_end_of_statement}}|{{operators}}|[,)_])literal_number_hexH(&[hH])\h+(&)?(?={{whitespace_or_end_of_statement}}|{{operators}}|[,)_])logical_operators\b(?i:And|Not|Or|Xor|Is)\bmath_operators(?:[+*^&/\\-]|\b(?i:Mod)\b) operatorsA{{comparison_operators}}|{{math_operators}}|{{logical_operators}}rem_comment_begin \b(?i:REM)\breserved_words\b(?i:Class|Sub|Function|Const|Dim|ReDim|Public|Private|End|Preserve|Select|Case|If|Else|ElseIf|Then|For|Each|Next|ByRef|ByVal|Set|Call|New|Option|With|To|In|While|Wend|Until|Loop|On|GoTo|Resume|Let|Get|Exit|Do)\bwhitespace_or_end_of_statement@(?=\s|$|:|{{apostrophe_comment_begin}}|{{rem_comment_begin}}|%>)x=rƵd7Iqv7)Y3iZV$JH(۷H.E ,v0s???g](53@>ggs9~i؎]7>q]wV6~g ~e r6{BLfbEKNktz>^}ױmyB@J}#vt@#G=x q(4mlzr_f*6fӯa3d>`6-DB6l<7Fϕ<[N2zf+-SZf[X'-?W2[1NRԣvL;´RnQZ8v:[u]] *n2'Ju91`eV;5o &e[L"!qJ*]w fVR:bSҤ 6D gFW\Cf_HzwJ.w0-6m;Uɔ<$Ƭ0KwJұd!oH"<4~MR`h,(WqI((,I&UAYh#$#`e\Sqf$.@sJR6upqIn$$|PJYے$.Aܑ$sz}v}W1 -Vܓϴ,r`XTcfܗ:y`0o$SAdqk{hڠ&V(M#Ζ Eջ;RiC</2E?d@xGs>~5X%u`Hߩ<1eIfޔADW"٧}Y]ItuA֥($$<,uzZ'qtz ɦ$qx*pvs>9k Eؗ7PE^̧[-Ҋd8 iP)a*\tB+"54~xh ofn>.L0?`1@JTL̨ Bkqd)9jZջ9 jIΫoiq`oxU6A2Y9bi㈈ʘv,AA?7XXg$&P*Oj,h=0K2ǯ UQ+WMPc'SCJMf+l?؄U0g]x^`)Lf2*_řL)*0*&>YabII!\O[@5jR( @zYsIc, Y F,do磍ZHQ-k&rw0Ӟb!q!OSeL#ڣ,S*ʓ?Jfx.3TR!FwDrgRC-kL0\Z[5[@\E&KtɈ0%B)E9N)1'پg&3 S!@״i+tX%y"b}\4 #M63*+DYѨߗ9TV4N1>j]sSdUgpfB~~#G-J?(c2 A-kwȄ$Nk` 9lΦ$G?Qq4ڽ0uxVr\)} Ťk!9C^ \&-@fv->z77۵~}[[37&>RbCMUk2vV[R(w \\k ྄4!{sR鴉}MGlH ͈L찱IpX7,l3҂[y i-1JehߗM u%@Ɠ|1-'/{EZ6v5ÛG1n"5?5~v{ذ:iBduq\{kvv%YADlƖt ܬ:?le ^W"TFfa##\ 2.`W+$ݶ-|G=s5ys!;8&-p`q7 &JNlo!L}_`{tJFW{w%Y->c%MgL ԥ Bzgr !+15}&;Zo?9ROFFBAྨx.dZ*#Y望6&<ϱ/TFp T|8 IjaxV;JiXɯP ۝9b oQMw)1TfSuNaj*҄;o(l&܉n Vɤ>u @THѹe%#*^ !Ğ7,AfyI9wl"ZO׉Hj-S#:í.ٿfb挘 }F.޴ybםXS ~o 4`i9oF5Edz Qkɔges#0H^ʗ^a fAi LAPԯ6i&؇:=.Ȉ*B(x]5mz"MH/_l;4z$X,b_7*g}їVaEO@3 W#п;0@: љnxoxTBElB?^1{X]6I7 c`Av6`s5K q`2vzL Ϝ76C!.ˀYF<^ ṽ;j5{}7ː[ƟWis?- zRYTt4 R vA)sO$>x.H?/(U_٤R6!4rh(+mGb^},UjH+(jWKO0Iʟ\ҁ0Iz=G,CSx-n~ ``A V_#.cX\ (6 =&:`poA3 , ё:AA! EN/ض۬\ܞ v8C[@bQJ2qB+?aEyGfkaܱCV)(V,`68#4?f2!ܗQA]7,t^ *-ų>^ߗ)^9S#f2٪8YASK!ܲczLy{p&zG瘻u|ݘ%G|qE\x3Mi4BU\;w +mS|y -~?<+8+~N2*gdzcZőHBDe KsUrX nNV&d p+ pI! 'QiSOar&OOXc$b_e,h_Ŀxb'5AOtT *rfC=>Jkp^EZ)̓~Z8o ffQ[8fZr֮9T>A`8ޤ8棱E k״;o|4l ˟2Ѩ˧_߆+J?R!PxfydGoG>¬Kh㚒 ,Qƙ }sN,NMG:1tsZI5 ,; Al٢:ΜU(h6 p#,hHpW\.,L0VW@q'QH^vzsG4)fo eXz`4g`X=WM^^=rySac&covW5v$z>`(nts "?Ғ5.,`o35"|q׊U'WD UBp HTML (ASP)asp text.html.aspxUKO@Zz11IkDjXcbj"քlaSB`1ܡjslgf̲|ю&Q`^HRDS7c1^HG^R,\zF9]Mem{B<ں+6$^|KB#@ۂUM'CMAUڬM߂FQ.!؏hu@6NFlAIRHi5[z \ʈh@BxǯdS#IZh7ۭƥ\jXUAJpXq@:/"GY/"K,*sZJ4mJ5;x lf8JߧWG-60"dTfu{}ZCǽٻ}2oCh,OԺ>R:Y0n dD t8hpubsilT\e+hrjQ:+xǡ1uL=0AOX\ Cyda'+<-'V ActionScriptassource.actionscript.2x[nFvn~$H ?Yo֛-wd{@h[3GZ3c6d+6ICRy3{lvK lYuKgagUdO{g|wML^,N dĺI㪴US03nL t®a6̝l\ p$9Bj‘XiRJm@s{Vbu,WN`yZӍ>i01R J8=WWW(k?Mx*jc8LO{7uayfWB`:a9AqL :I^eGy'D_+6;q1V '$ˆ1VTZHrFѷMhM >``e-Θ^||% ;cz Blb.RztxV~$HwdEJ$u/]k-ӐlƦFeKS]_wSUM +¬/,NI-x!0iJ=@QV{\:"[#DD Dvb%M0jj)CsQ 6VVk"V`Q̧f YC"š%nís݋q7$.5i%E&|1aK`܌Ȫ9}A]nT6R[)IU4-DCQqL5-n 0 iT_L=q}wHLr|C`a ~)\PL+ v~[,y_4V aM.W E?}Vl!>1`00*S rjN8bSxWIP_w:$Ƕ+7NGA~x`kM]|-h,K&G]`ڄ/7D|Y$?^ Ep}P,_Ӷ^u+&IoKSiZ8ٴ^ x[JrZa { _6W=6?}$%IgԂ 맫X h0ѲK)'Ϥ"MV&h17hPC@@9:pHqHo7I Gp8wEKqW/I+XcC8 !sSӘ0HB! ĉbN++D4@d%7[ck+Q. `"± dQ0_nERXg*".Gg֚^U5( 2k(>5|C)\n<5T-%TiRTP^)ej:a&@*4횤<GހsQbԶ:[!_t,-=7%**ߍD.T\ZBfi'XUCC9ZR7zO|rxO2{OtYn ZImIe2צAiHƱ&Tm|c-IG?Q XAÓrhK;O,GJE费 2bCZ)[0]`cyE-}?h{pX$Oڌy4A.duu%FH߷5s')É<2B1 @"@s+Pbyufe#ܶU,NƏ6d ^`O'V~ ^1ha)]^r΂p;ۭPedHE@Bm sIaM%і@o,[GjsQ{*%84HHXIJ՞L@a,I^n1dX9M'#ʼtY2(]頃KqMt^`-Q{qiض;5${Xţ6"NvWab 5J޵> fqBf\<"gX RwH"•YS2u;tE3hyiŽ,|6{ojtM{[wK.~h #Qnyl-ZkvwG;mmr6GBQ־m}a-D鱮uHWPZf}tО>o{ߕh n$U |ӴG 8bty5@ZA.hd`=lhCYkO 5^oM+:PZm߄l¡c[޶za6;YR x6D WDl= ;ZW=KN^7kmzdba^XOGz{vibd$_p%NF)K̠HMvګkn]{=J?ivߦC B<2 wi钁_c]3>ªYRpJw&rዎRP}ܫj%nw%6YAѭw+Trh@AAFwL-r {!0׾!ʛ%48q>+dⱶ:<ӿxƒ-h"ߋT:[s ?/>ur97YQfn6+y7 x^] m"ENҖ8V*0[YⅬ oyPj=RgT;ϖx4KE꘥i Y!}.7N1w͆7;5kgUOt"n֔ƍ.ߊ$V짺F_=I"䕬8_7TBqRHԏk.+4&AJ,ނ$tMdb"a[?kTQjz|=lӢs ֯$]rGf e6VGPŻǯ0\|bܗן{,3`Wl_?{~78b:`2=>;^xt1b>u!g'#7>/N?~2˳;|9{58~wSt|p1~5OƧ㋯䦃!MΦC_\8wΞ3rGI';t>[Ní=1? .ϼ%#cO+~D_n&_.[^>nWxovW}oJBgeu<#AIW4Kޛ죶}Ðy~1hŸ>2~o.o^ h(ojx+o8rAxjMHiAzeiU^8_ 6,xsujZf, dmZ0ձ.7p|1(u4sf#k)U|2@hps P1Yi1'X5H?,,X 6[f!yDT7lA^bMbb3&Mvm㌤izn>AIbC@0շu0&s%qV'IOԩDZɜ٢oH\hnlDR670#6Iک1Kpնk(?o`5h'mdC#"tG˸ YuBe#ʢ;iv$lj#<\j)lDa!`ruoy/^[QH`dI Ci3>B59mP<~hrs[hS˭('fh:-Q,cӏ:ReuZ#u4G0rv*qUW} YBHuL(lZޑVXZF,[Gdo?_*j'%o&ˋ fOXpwM㊃}yqlkiUh=ж;H$H~y:VTR/?H* nL<]'yi'O1d®NKFxÆ-q86@_O-:2-^gt 'h 8j<!bGA"zΌ(8pGGa4c>24+elDdo!'$hGphd_ Vy&)˦irdI1w+6SC.2bzDoFUO= dyAGzdč褎aK2 z[Yb3ẃB/*H3^2ud$I>y+tĠ>;ɦ]ooy$PFW  ;qƠ?Ti#Ï c-,$mj\3|`#5kZ2"hcb_L^S;Ƥ A:i_=VSZ*{rS‚ЇV(b!Y ^>4])ӐaܤsL N М hni<:>+ZӃ1kx~AUsQ:v-:fCzrs3Hq-@E^2+X_:;Hqkqr'񺓤z\㘮{ʈu" 8$>-}uH_~;=%`YCWoӃƅNe%|9cu 4.Deє6Y4jL_*3p׍)NSTFq +LD'a^6 LKf9 (wC<*;[3n`R1S]tL.hʀ+G[5)W,Ы}W,{+vۦRejAU?=Ef% u2Uܦ+6AQÇEmEDck0vZd*S5t_S:LX-XqʄTr"\UbW 0Ρug + Waue.}V,kb60V &v`b,5a@mkoȖadޅVhaƦa la`<$ޕa* $ILu2µ nXOVAjBR2,tj6ZW{чU]8 u&0q W8E&@ѣ- eQb KOT*6$pkbgf>٤:*6uƫ;P`W\[8h!y#pEa^o *|t(Em WtD5mQwO/sw|3fpoh/T@{BIڭJ>Q;T~ ,l*x\pHs&!lA /#?%ID|[qElRomu ee@2.an`l[pd>ejdy3|τ5 TCH~!B2Or%Q2q0*9CNaX10yapF~n:}.1uK R;S|h'kðK5U7>c7!46`+~ꎧprq+ I?#[]w gdҍV˯Ǚ_%{S2VM,gHԂ_TиG>ωK_P^n*?JC3?sT s A6b70y.bDC`UG 8O! - Clv,r$A1lb0kpHB@9zx/SOA)y+tC1beM6b= `ï<[0]HDEB=?>}_}q2KH)WC  $6@pLmN1LCb &&~LhEȥL\@alg3By6m0R쁚*XIќNڷofm G@0r# )1`TԒ́.4&db%K DѽBvvJ I"fDMבE2 _D] Z%H0^p&xOB OXX {cnp9]H OzɌSiJ;%5E@WIH AWnjrsK(rZx%2+C˕_AhH&1g EV\ُ\1G>L;g._^b%&&94L~g3Ź A}W.I4+'1|@bg /~KuOGX l1r8n>\|F7NŏEbI.]¬x~ %mZx!?:{bV#LJ ŔCU +ltN|ZGq@v`6Lv F`v9[rfxfٙi[˘8(AQ+*@ۏ¥ yXcͭeXP V1&J$#ē.ƾ؆x %~ΥB)~,*)w8%b>)O%M!]߬S/vX`Q0qw7Jx\pN@f0*կv L)M7'&UlZM ] 3 %p݀(K`9eS)$[r5.C[Lo=V%tb,Bg`A`<txRqo0ԫծ8n Q0Q[~(W;'No:|CwYm=ԟ֣gV,놜[ Ft6^^m|fd8Wϻ);l@xS7@8WC%iI)ſP}yW69WnNm妇[odb-a<`e^P˛p ˜T]Eu!lyOK%)DrQAǫjݲɰ[5D~@2AsF ~E%(o|jT!ִlOnnF 0F'.IuA2q}c'}+3@Tƙ@qao- NA: ?dDJ y_h飺bVy^HWȇ j)fXkMࡆbg%<~OJ'3'u'wN4iʻwXIHç܀(E8sT$'YR5(6vJ08t^_T60?P¼p.dkc5Um~r5VVfe_a}حpS 12 f<\F)̃7$ĹSȽBT<~7Bqp>~:wR*Bc0*v:(R3Eg&0&jq@c1 !ɦs?dSR (52SEBUB||~c\"y >P+@gڈ\U7 3<80e]Į3̀LSqzp {5VX|SOm;ebh4*z֚L;<~dߩJ)3y~ r Eb]eXe t]&áE6FtCk`y]w"oCl [dK_}uNhv4m53)b1 PuS}E<>kPX w }cY8hpX-=K`T48"V3P.<5=ubAP/%&0Єӓs܀fH hPkgУyJ3/K&HSAvu W%.%`TO&BPwQڎtwpIx'eNs!`4cµ <ǽ4qPy\ tT0lQrI1ŝ—$r(N*ۈTc>Vk8㵌w߁H&VFSzSk'3rFLGPǁirhBJPy Batch Filebatcmdsource.dosbatchcolon_comment_start (?::[+=,;: ])command_terminators(?=$\n|[&|><)])set_arithmetic_operators_quoted(?:\||<<|>>|&|\^)!set_arithmetic_operators_unquoted(?:\+|-|\*|/|%%|~)x[s6Wzi;+Ʊr5ԭv?tP$(q$d9L(RlW|~?Iyݎ؉ЎAc;H>=>Ŝwt꟭9#6[L7Zg%%#xnu6]Ewy6=Eʏjt~.M- ^^lۢꥶ-/mjJTm[ nZ0`4 Y'LZ^ACzzK ToRqNVu inw4)MV=Rz~Os3n5U'IY+RӖjU0`R>4V:e3I NŒ^w?ܽaF`K<: Kiu.4ܿ;Ka9z^žg};J*t{]; QM6sG;__ 0585R95%8seqT> hE?:AvEpSPAt:Ok8#8cj漦B, ]7v>aE&g >ԥ@ՋMoh X hW:5YҴ΀^6k[1{k\~{dMW\ҦJ,؋LI@%8u{*ِ )]5nak/5ը*v~6xM\pV~5r5~4YJkU#K/b_ !CȢ2tjsK|ʳ!Ugwc-'~SKUywJbDAYimR-zk행 Q2E&iè2'|4I4?4H2ir ZVu+Ms7cK%lYᯛ"b|4NMͣ1_?:{Nܜ33uq&9Ėi%bTvVx:u7,╦ַɼ~Poa_ZL6Ħ8{u) Ҵ=hu)AuݹjwiB5bIhV딶#a޼˜al]7_2Im엫cd0J\a J$ST,v?Ǟo;'E;;.s}.qo HNc;A&4KyNexlFă8R?8>X:p\\ T|0PqcAC?R7p@c3#dHB= {㸍y^1ʈq@ceN'Sڜ .\ pR (.&@ g'Qplxv`tRle,]y=h"HCd.֪V@ ^ 0'u`{}jM'B>c-#<^-J%ür@̽A 6Ay+ӄ <*9 8*0{ =DO ҖŐ8`g} A w$6ɂ8&0;{QBt (bCn{E>#8}joENC $'& $lS`{OԳtȪO! rG k]K;Շ5b)TNا LqD O]AiC1u2$ɒ(S;,$R߈d;B:fxrrTZ}HC4YsM4CϐA:lyu`R`t8J d7OTEpG",~r)XT}e9eYnx(ƶ  }"ʋJ4W% ;$,$Iً^ƵZm7bDt,K;ҦMM54MKjWgj56?1L4ʕNe~ȿ9|M'= 6ʣ#aȷ w ooo/zN`ͨFTƔ%ɔGaՏ:bc%ڵ =z3ކ losm߬jnx WȳeGbBD?zVM1.QmP5R!_O8|jl9' W4 pe|I)e}!v_oB2O{j]@ԙjK3ڄ`;Ƈ7cR N{CvnΫصqr6,y߱|u]<~ctV@r8X@YjU٭joteZ1^/EK/ۛ~8OgˑU?hA,־8Ŀe&;TO߼T[`\Rl\>÷wfʕ<;lbzF,{4 ]D,|[^Wz1[ Te1WSFxNv-I%^nS$!ARBZ9O;_HCcsSa"<䑠>q'[D60&J5hs*q2lNV# 뉼Y hFrb RM$#Bs4|l#"e}. ٹ.0[f]mu$̧~}d!LNo; z֔P1Z^ qUi~݆[eJv?l9mMܪIk|]TRnbIp<JSVc{"* .ӬLRyJ22BV{<:8H} os3cN?p `} >pIRC#cscsx source.cs base_typeg(?:(?:bool|byte|sbyte|char|decimal|double|float|int|uint|long|ulong|short|ushort|object|string|void)\b)bin_op@(?:\+|->|-|\*|/|%|\|\||&&|\||&|\^|<<|>>|=>|<=|<|>=|>|==|!=|\?\?)brackets_capture((\[)(,*)(\]))cap_name(\p{Lu}{{other_char}}) dec_digits (?:[\d_]*\d) dec_exponent(?:[eE][-+]??{{dec_digits}}) escaped_charE(?:\\[abfnrtv"'\\]|{{unicode_char}}|\\x[0-9a-fA-F]{1,4}|\\[0-9]{1,3}) float_suffix[fFdDmM]generic_declaration\s*(<[^(={};<]*>)?\s*integer_suffix[uU][lL]?|[lL][uU]?name7(?:@{{reserved}}|@{{base_type}}|@var|@?{{name_normal}}) name_normal{{start_char}}{{other_char}}*\bnamespaced_nameO(?:(?:{{name}}{{generic_declaration}}\s*\.\s*)*{{name}}{{generic_declaration}}) other_char (?:{{unicode_char}}|[_0-9\p{L}])reserved(?:abstract|as|base|break|case|catch|checked|class|const|continue|default|delegate|do|else|enum|event|explicit|extern|finally|fixed|for|foreach|goto|if|implicit|in|interface|internal|is|lock|nameof|namespace|new|null|operator|out|override|params|private|protected|public|readonly|ref|return|sealed|sizeof|stackalloc|static|string|struct|switch|this|throw|try|typeof|unchecked|unsafe|using|virtual|volatile|while) start_char(?:{{unicode_char}}|[_\p{L}]) type_suffix(?:\s*(?:\[,*\]|\*|\?)*)type_suffix_capture&(\?)?{{brackets_capture}}?(?:\s*(\*))?unary_op(?:\+\+|--|-|~|!|&|\*) unicode_char(?:\\u\h{4}|\\U\h{8}) visibility>\b(?:public|private|protected|internal|protected\s+internal)\bT1x} cu0ؖY,[$ K$EBNMqӤłX,D1\6Mi&m&iѤפiz7I??љٝٙٹ Z7{oN% ^/{a N؏ i x$y6Ask}gUuΙ pg꣆H 8nX\?c ]nPÑߙ$)$ 1B\z#9 =<ϰoCɍ!3xRi!o*~XHj<$p`Lo6N{Y5c8@e6xu^ww~"p'pdΫUý ͩތ.ނ.r4unQrI FfI%…{zWE4aIdq{`=fСo1Y2rd|ex/Ke >,A7/V\A%e$MS%N HR͜&c'w`^tw8\+0rS`f6ep$)g$V q5xs\jg77*.ܺr{| Ynf]8ycF}ٽ:cqӴ|>y02  T G z5dt{ 8PJ_1qK[A K 9y=) ൷ /b헓pRLz_MPnf&aB |]_od+@V/ECQU`U$C0a3`accorlƥoqxtQ ZJzڟi\" h0p\Rw8&ƁqsPA@OBC)]42`NEY3ԁA0 o`bm߹*9.'d =A)kgspg#}Q^pܐ;B*`ަ_FW0~Wag:[._D ¨~ "ʌ[>`Э) fs ox Dpa7Bۖ/P%{ ,0Jv&Q{#jw^D(.P/؈F=' ;(: (x?Ƌ>dA Z5nuޛ#}}'{S'@J7/]VW*xμE=zSE=X-λȝܪ3,2Zd2:D>JIP$]+w2v6_V8S64 U }d{lvo{@@7 w `fCAW+:a{i1]J +dYl& w)$9bz:$',b p 1Ȫf-#/JF]Tx!44"B\ьFjë<'$*˿I\~W%ᅵAs/~﷛e'@Uc=ԝI!] WeꞂ4 #"t"6] P G(K kVjPVI pa)ɇK+vjk ͬVCjks9\vЈ`W+XVJPLȘ[]cɵJeTG0ܟ}HqL"/cF4?ܻL0 &l<oFkAo}(*XJPH}ic} ^ɬyy8JcRT9}rv`il|MHg2M{i_guQ1:D<$ &XUFT oH:uLJQn>MTi`NN 9H8W:µvcjpOiJ[Jb#W<(le:"-y.0 ƻa<k,l"$벊5q&_͉x7>{VnU!: ѥZԌve絯N^884Sk{]CyKҩDEsPG3\gJpo>C{-4/Ễ8YSȮ%3I'0Iaߧ%IǙ#,qIC_=ξ( c37*'?F `*%3\}!s2 swSę$(_!v Jc"+J@bEx#URus[F1=ANF)d\Gǹ\Q[0bxhܬtB\pP`}F|F`VMsq)de'-L{#;WNdf$ #;;DFpUҥZbT;X ]1\]Kbh~%]*e8-ɞr9"8C -jþ?ZҘ\J 2PNSVofA%R6d'd"#>91O3VN -G#f31`i[o63njBTc(y[M;{fIC63HFc7dtO CE'˂>^ҚUe)D`&k Y;4#48E%P|riG%i8–S":1s dRZT-" i@5L y1qQSHZa81Θ5#uЕQ!hA& 9Tt 8^G:K W5g[3~#`Zo$rBR#W2Qʱd#DirӒ46i+d1oLbZ ʙ%g¿(uW8h̞ΰQ'#jw`:l5Xus^'s⌊J n,9<FltM^K ZK^zh~T̍${/q 7/ 2oeݭ$!moz t\4̙CVS SLN [n ]Nc$ZZf2#? $R`@@[fZZtl 8ad\ewPL&ɾ&Bp qă J=g,l#fa5x !/3MNFEotAxT3r7V(j9w.AtKcOudOkB3c⫅Z]nvLm KS *VRO5,z~+gd2.724ʥsڢeF7Q%ӈr~&S6dRDڔ{[:ʒ餥08+'t_tQHxPM$ 5Ԙ; lNँ1RlI2 q}ײ/˾Uis+u=S6:V[r84<Or )9 ضLOʮ -#o2Ѧ'@mG 9t fٶ[ҋӼxc0zJwm m0[3ЭlMiJoOMն~Q D, LbU@q`+jJVn`$$c],L;]=.j^K{n94O(SȱgNIlD.m|> :~\/ ޱ8u,0o2GR- (<4aKѤG,S7S,YbE//T8UL 'ibFET/Mc (Os 2n^,W7dwiP?w;J4< Y[fS] N]rEw{Ayľ֭ `)S. g{+WT^;]qUh+hB{}ڜ`A͛7)>t=@Ua:29ޟck0KW/S}^:0a {-*?gB>Y(fLGelFedsdU^eŻY0q\o+<#\%1]\K $9"6HbyP{m;8wWAѴE+ygN#Ɨ@_exyώo蘿n-[0ߝ}:'Y 4iFsaY 8LP #cvƥR^' (d@ǻD˜#+sDl&.IKu)8lW.E bQ)ؗTFpT#H70$iih>>4Ĉ HE(!@ #Hn1;KfҸG|dTtŸBp%,efrxwhLЩO쏝 :A]Aϑ̠F׊>$eUJ +5n&-۔F0.Ne{{j#WXZ0I jU }r8-!X b 9rf25Yi;nX_*_VXΘ* VmJҡ'2UF]B-=~ ?x )o_ܗ[)7y7) 56zˮ[t7kҼƳ{O/Wg>Nr #gŷqVLc nR+=v~8z62b#c/)d#owCG%GL3M+XAHHKi$)7obvn c|4giS8JӻC_lw9rv?RG`$;v0/߆?kMS:c9.ec(JQ4QG% oq{UBs{f&ܯ",`|TFtvgMXc#3M E^!h "^J:{\DZ?3R*\(ޏrH Յtڮ[t6w2s_s̺bj ql[5åoeޢa]4Oɦb L5"=#v3vqp O͎~K&O'Ą:X `qW=P5X j M0 |I#/;I ٣P~'"n螴٘%7kaiD83ThGˆ-Fc&L7'M6'8R1q5t)kOQ'Cu^6Xp LwRzuòU-ᮌei4Pۅ*saXrB?!RHO6Z]bh[RWw.9 Gqn72 O> ꓅ @+VzRѧ䝎swxJdc%}E׸EnԿvj?MJNr#}1-ǜnj@ۀh1ϙ*t6|L@=,x_.CVC:=G<=d~f* O逕MfJf W@)?Plpd|Gues_ĒBOGL/I ޽,!:#Ix8Q;N2%0)Q'lM1Y,dM~X?$(q"EE#ؑs"R= /LjcRތMO]x WĿRK^N.5c~=rKgRTC vh?*T.KPMRsͻ OiSPwF;flNc ཊ})hƴe}(t=^b5i:z"zdĜ RPtuXWW*,&?kn=r##>o"Ra4S-1EU-Z\JrkEC8I7)PǛƗTOdIݷ>ShwWR)A'Ge"բ=bk)@BcR]`4 Od&?Hbb,wkߝ=Wr-挙ttYѶ'bbYdBJ2eLi$_;G-[F~FZtjFWW"u%D+`+ 6+V"s415yXUAfay'_g2978S!ЈM.L8JșM{kBvvm {1r/^s2m-H _5tK><^ c f:c}5,Y_ z#_ TaԒH3(3zݻPYG%2|K)}lOGJ|k{It,1{e)`Y{}`A3$r3G l\摾'iQ2ZBc^sI{=i"B %u PG7!H 1K &J *C!\*z+9CIB+O*J5%)) [xޤ;uZ XG;v^k:}{D,EE;v4bϘn[-gBP#Ҩf>.ۚ͜S?,a9$Yee+]^~  p؅By3LRvcLqՕySLW> (PxÙshqqu]h"A6ЍWٮǟ1&:-ҁEA8 nʧ$dT$ϋuGAXk7W,8? 7^PaI!$)'8 w݋t^sڸ]rA9Oc/Hy)R 2ܜi{f|{V"@#'.ģ- v8i80vTAkWbWgEKjVӧ\JnG^ɭHk2(w;I pOMdyvFJ⭅Lggb*U֜"̵T6('+)) ~k\yI1`@ɼBvF'r^tJ&ȏs9h5FQ~+I5R ˁ'%u|.%f)>7IH1A]ؙo@Ǩ߾f@lʹ DbȔOUB39TNߞdN qY4 ٻPOA:NJH><&gz6uGreI sR/(aJy[\cVr, 1H*Rw)Oꃃ-~䁨W)उ Ì |lt>nޓhlMiAp0v7p4me$O*ehYL,fƥݾ2DX0z<1vp{J4rGigcaIC›zB]8CT Zxb30#G8t(?7)ARu):^(ŋsK}}*Nfsg Fs^ˌJ+@AfŽE Xu+&@qDԐ2A f;ruǾoW T,xkdhL 1v"[6X_Ȇ<cS\4aBJSq9^!m6AIݞeRg0͒eNx5gv` 'T7vwLg2H+ȵ,+3 Y /*LskL?ه]:PAɤ>UZGASAL'8zPC{f7 K*C++ cppcccpcxxc++Chhhhpphxxh++inlipp source.c++-\*- C\+\+ -\*-#balance_parentheses>{{regular}}{{paren_open}}{{regular}}{{paren_close}}{{regular}} basic_typeseasm|__asm__|auto|bool|_Bool|char|_Complex|double|float|_Imaginary|int|long|short|signed|unsigned|void before_tag2struct|union|enum\s+class|enum\s+struct|enum|classcasts4const_cast|dynamic_cast|reinterpret_cast|static_castcompiler_directive'inline|restrict|__restrict__|__restrictcontrol_keywords[break|case|catch|continue|default|do|else|for|goto|if|_Pragma|return|switch|throw|try|while&data_structures_forward_decl_lookahead`(\s+{{macro_identifier}})*\s*(:\s*({{path_lookahead}}|{{visibility_modifiers}}|,|\s|<[^;]*>)+)?; dec_digits(?:\d(?:[\d']*\d)?) dec_exponent(?:[eE][-+]??{{dec_digits}}) dec_suffix-(?:[a-zA-Z_][[:alnum:]_]*|(?=[^[:alnum:]_']))declspec%__declspec\(\s*\w+(?:\([^)]+\))?\s*\) float_suffix[fF] generic_close>)?)?generic_lookaheadi<{{generic_open}}{{generic_open}}{{regular}}{{generic_close}}\s*{{generic_close}}{{balance_parentheses}}> generic_open(?:{{regular_plus}}(?:< hex_exponent(?:[pP][-+]??{{dec_digits}}) hex_suffix-(?:[g-zG-Z_][[:alnum:]_]*|(?=[^[:alnum:]_'])) identifier\b[[:alpha:]_][[:alnum:]_]*\binteger_suffix[lL]{1,2}[uU]?|[uU][lL]{0,2}macro_identifier)\b[[:upper:]_][[:upper:][:digit:]_]{2,}\bmemory_operators new|delete modifiers={{storage_classes}}|{{type_qualifier}}|{{compiler_directive}}non_angle_brackets (?=<<|<=)non_func_keywordsaif|for|switch|while|decltype|sizeof|__declspec|__attribute__|typeid|alignof|alignas|static_assertoperator_keywordsEand|and_eq|bitand|bitor|compl|not|not_eq|or|or_eq|xor|xor_eq|noexceptoperator_method_namep\boperator\s*(?:[-+*/%^&|~!=<>]|[-+*/%^&|=!<>]=|<<=?|>>=?|&&|\|\||\+\+|--|,|->\*?|\(\)|\[\]|""\s*{{identifier}})other_keywordsmtypedef|nullptr|{{visibility_modifiers}}|static_assert|sizeof|using|typeid|alignof|alignas|namespace|template paren_close\))? paren_open(?:\(path_lookaheadC(?:::\s*)?(?:{{identifier}}\s*::\s*)*(?:template\s+)?{{identifier}}regular[^(){}&;*^%=<>-]* regular_plus[^(){}&;*^%=<>-]+storage_classesBstatic|export|extern|friend|explicit|virtual|register|thread_localtype_qualifier)const|constexpr|mutable|typename|volatilevisibility_modifiersprivate|protected|public-'x=gxGRr;LJtKNJbl?YI8Q"JJI:'NwzoNޯXbHJp`0 di$]oi6Surņ]㴚[ʈb^(RX?IOJvɕݮB%햝kRpb?YEK8l ݚr뵜]i9[vj-:$&*0?9->GJ>;V 9BBhz֞5$ݘ} Ii+Zɧ# 7Ԝ[ :jܬ]X{Q+fJzA#AX' qiHQ-^LlN"İBkb5LQ+N5R r!iR9>]6fr0(łs$]7g7Yn83)&kYI1Y+&&5faU6k8j2GZET$mòV òv ò. >£$ž{1 ֋vna!7&@bJJDhj>lκtDRj(UbAXPF@^xа:(Vq:(&tPLf/e?_o90EJ `hbXi:E$3]*9f>2.IΈ4:T謓Bg?&f-V (Z x8-+' Y@ j8s:Bh$N=74|P!S|I2gR:OL$#J '* [Vךb=IѤ)))dQ`4T(MXEKZ&iISӝ8xI-ӒdNYkK1e %L hEr8šS!֘BZtXk bMsI5,H᳆I诒t!JfӗQK_kKba'H:l $\p =ISRZxՌ˰6IOE Ͻ$7\wr>q$&vQ)}$; ӳ['WF|dZ @RۇGL]KHJ=f9(/4пFң5 RI=C4fCna'Z3I((StfždmQgY֡h!34PQl Qt("Gҳ"$|U A 'Bh}" LCK5f aHa_3 1SC Moe"'h]($fnT}hj6QLBMq_&(nѩ $[ $/y9x8Ǜ@NT%z,uw*^!+ho¿R*x8"`K1^-'DQ_CғeMQDA_KҳePՎEz$&\T7HF`_XI4$]!7TSo iGEgܞ y+IFngV+o#)5並٭r{.WRfirJ;[p2Upʁވ}bv($<zu T3N nHݜ4ڕ 쥧"- (<>JRjz3w6$}\*-e('> ҟ?)URGYU>%/OK^a~5wD< k%ϒ4pLKR4.DH5ZIz N4VUu\sttM IFѶU"~T~M2xR}}C'.ҫ3mq$酩J!J d <h|Q!.:ZD5}KPHv9 ؝CQoB#:e5%Hu"X <'$@dL N+gY)IUU)/% QTExQC$v?QK\ߒxwޢ`j!]I(9!捸,$KdR0W>HX7J5.zRC;phUf Y")ؿ$#/RտIO+O6Զ7ʓe[򥑑$λ)m}xJ';1^bkքjv+Vh1ȷ>Y +PmRKNZBVrc2dM"M <:䤵}Ӕg>y>S1ZK#<"$p%SxpDe)PnҍV5tpBitԢ5PZXpAf{yroWaМgf4Rt4PӀ?W13mάzs w/yPZoNu\XuKHѶ}&*SΰCd'FGGQXs~aaF'BsĚqevu sz7%rpyuD)&Bq@tiuyzRM}qB_zA}K-*5?++mTB%47ɣjު . 5gK XX$] oPN\cR}jd{PV (}*~MZ>ݠm J^x;/"z5AC4{HӲ\B}_u râI(Qy][9P\M gSѭL7-@U=BJ{+zaUwST+"L~Zih"6+r@%hzt\G+$91Y؈ut(Coq9$JPX}gya OTVVĄ3IA*LzNʢUFvbv\x/3I~@3A"TB`9F8  & _q@tJ +ei7$X* &`yVĴes cT EĞliP;SvP5lH,4:b%j5n :ʎ'[u=&pرLXc(ZY`<ͬ",So@jMW <#CIW*@Iܜ'rbJ} Ypdme͋`ןѨE!8]}mÁà +tyZmX\`?\mDʅ^vs)591:|ޮ S{|9Ox;v6l ?0Rr94( Ox,Z"& S޺uLdm ބ.n5JOb, WWQ`"h3K?X-._vޯ2=L!T# MѺHe5ϝK7g[&zbR\'o1nCg/wP6Ny[B= 5T Û?#CyZr+*]Q1FxbZ׸(97J^0>ƛ2"ey0ݗ xŭ b;=]k9z:Fwgꍖsrv-^@ߣˎ./ՄbR.93@^1m#fnEYg,9,ׯnށ%0L]6@.5QWiZw^3 gS4]1nϤ1#z:pBQKA/TDJ4D孄k(%dGLj|Ԯ7:`! M\;ҹ29"8p<>O*qӗ,RQtlr_2,KRҽ8+&Jh@ߥ~@&:4$jvSO?d~@X{bs,f(lz .tә2>YDkݲC0q]K\Wk\ZV]$M=VA[7}y0|Aݨϣto +xѼ8r۴+N] m5*j]!,dq,(W;MPF2uO®qZ\ŵ?Nu >WTAsb0<`tUFWy#"q  cov~n߸{LwbM^69 |SZ[y[Cɹ&s#SeS(oDcٚbT(=B.ŭEwѼ3h )s"A3dѭٍ2; %s;DGK:3Gb&.:eXAK:ĹqT&:.7W1 kvcxLZ 6lu؎)r &P~SUYtG MXbתH>MB_35ƌjhRk3!lmC&>f @uS6&)kAh$yc$0Ai,ڿ(].n#T?(T?Z=nItx.( *z.0lqfKwP'@u QE1Ʊ`kFdCG 5F˕vc~ k}zJ&tҜ7diO` ʮ=S7[n􊌴}F\1vuTqgskje/K-dTՌu)AA.>-G3"5b"1߭ʹQT`ASb貚aD!-;=w:Q]r8QX}gqQJ&xdEBUL'%"*d=LeI'ӗa*ɢ  K 2"&RgjVHcJiܓN445,V4Ii}EgfMz?aL /ƩzۙHC#7T8D)֦5.i h+jّ_CMwVV/l;vc2&HYh޼h04Tg_h0ԻTu;YGaE~-E1xN}Gd֠-xX 6)thRΚzbju[W UnRB=zuT*wODzCcVwO^gMc_'>{$pRIj6[fTIyt$z,!]:RYd '>3Ӑޖ><909e%8Ĵq,G]"+bcgq$uUɨ}IF.MCh˗y@)Nū2@ic8b' mneJwh`(̏x_q. )]@Ii.p M!>HpH0}z;+Ŧ7Ga2[rR}J1o7ʹS=K*a;N.$~4Is\Eu)a!Zݼ~D| TVi'v*ͽѩWvy2\|AS|n&ߜQξ73:*(NG66捍 !^ZtWG/rx,N+z#eNZS#)+x54;֟AXt# , :"EC_N: Sp^4\)cXL JqΈ!vA4G"6@״xOb5yך8UTHtrr$m6!|[ڷ!4j gHſ@|jZItxGHGO; Jƈ- cu^> rzyq)qxQ1c68#V'3v*rq,Pe$6*CitNcs|j{EY{\XZ8k)IFGtH>MOc ܜsJ^Jp=SAݬaI&>Հzw2J/\2@ .1Sj M$~>f:W9 amҜ;MHNLZsjmCRs&m{ ~xYrOzaƩU*7htψӗo_Qx?~ S@^J~ğT^oBS?Gs4~ )Cރ@~ڠH9@wTiG\G&C07}֌U z|a||Y3/Nuk A41IsD^|n ejыJi}~˪P b51ZOPqNse55תNB[=DG˹8"yY7S_)JdtzH=31|!i ɰ ձ> ȏ fNSȏT̷1F)W.X/6UClgsr I2ş#@OκxC!t:i͐VT·To. '~xW8| [y[!]~B6xjO]wEgZ묎bڿ3q'Bp E<)ZA[C94Y"&9( {AwzR+իbďiP@̙K@%oOXu,\L18H`^okӥ}dFf6S=h,&H[h._7 E4AVv塥?JRlBtqiNt"n,ѱ3ӗl@ۑq0uxL7UX*kAͣbZT T[3g1VQ+NX ]^Y ծ 3:w8`!q39sUiyh(nYxPș܍_PH(K/Rܸ1fx(U9Z|LUp[r:ڎL%Mdz5KToHL8Q5n '2y_F3!ȓ "y+A&KFRڥGdcKzX`\%buG3EUl娀F]";9I !$؋CW$&/q)|(N2MYmȉ- H5yҡkZiBNOۭV-[4V=\hI|薆u< 遰lWʿiH4}=#G{aL*<ʘ98 ¶5]x~m; B 午eH=F:]Xgy(4 \1*պMV5js%@@BUi [ d(O{6ɹ6*hNJA##92Ouǂ6wʎ +[޸|Pe)G"_}$%[{Ol]=y lc:R@ns+8 ǭPb-#㳙nJw{y/Ng!vQs;q9<g(3oUgBcv}F`u SSE--#_Såk>jϻ&V娔2pcW6:/l BSܬ]jԵ2ܨ?CSU]M1& XȿǠ(M>IݗB$j@Yg?(;"tGXظZj$ 3 O=q/$} mTY+>X2 J4VU!MY$޴]Tl6M۶S0d8-+LoesGe)ehˢ#us _5_]PǗC#݂mx?~Q1Д ?#LB4^h̋`wuLLPJcH/A]'Z`X* 77 ¬J K{+ 2tCp$h3_Q|GlR^H,A006 Fz)cJ;t,W+sc!蹁°Gkx%K :/TEv!"cSѲ1/v77\*MXj>clfD==\7"oz}8]Y&Qʳr dcۍ[1bȠF@[*eDՅR]vipA@ gՁ<ٓ8}!o@/3ꌶaC2pL.>zi C6 nlM;͈;Gnj[v*kYzBhG+fw!B,%揪]i$P Un!ZR}XQ(;†tQpXyڸueaP5ູst% J%١=+8)JDvN I3ϟ Af^+YQbZ(쒍ӕ~wȀ`C2dxK^"'ά%uH1UQθM;ֱMrRG~Uph?B;K+X ]$4p/[u]ʑBapja˦LU^Q1q1VgL'+ni9˶їy DOp/|H Lvmv(`JVYزSAJLߑNKYa{yF8S&vFOT\ /iO\f!ZoyrL4 5+v{rLlCʧ#N?yCchsource.c-[*]-( Mode:)? C -[*]- basic_typeseasm|__asm__|auto|bool|_Bool|char|_Complex|double|float|_Imaginary|int|long|short|signed|unsigned|void before_tagstruct|union|enumcompiler_directive'inline|restrict|__restrict__|__restrictcontrol_keywordsKbreak|case|continue|default|do|else|for|goto|if|_Pragma|return|switch|while dec_exponent(?:[eE][-+]??\d+) dec_suffix[a-zA-Z_][[:alnum:]_]*declspec%__declspec\(\s*\w+(?:\([^)]+\))?\s*\) hex_exponent(?:[pP][-+]??\d+) hex_suffix[g-zG-Z_][[:alnum:]_]* identifier\b[[:alpha:]_][[:alnum:]_]*\binteger_suffix[lL]{1,2}[uU]?|[uU][lL]{0,2}macro_identifier)\b[[:upper:]_][[:upper:][:digit:]_]{2,}\bmicrosoft_types__int8|__int16|__int32|__int64 modifiers={{storage_classes}}|{{type_qualifier}}|{{compiler_directive}}non_func_keywords<if|for|switch|while|decltype|sizeof|__declspec|__attribute__stdintVint8_t|int16_t|int32_t|int64_t|uint8_t|uint16_t|uint32_t|uint64_t|int_least8_t|int_least16_t|int_least32_t|int_least64_t|uint_least8_t|uint_least16_t|uint_least32_t|uint_least64_t|int_fast8_t|int_fast16_t|int_fast32_t|int_fast64_t|uint_fast8_t|uint_fast16_t|uint_fast32_t|uint_fast64_t|intptr_t|uintptr_t|intmax_t|intmax_t|uintmax_t|uintmax_tstorage_classes#static|extern|register|{{declspec}}type_qualifierconst|volatile windows_typesAPIENTRY|ATOM|BOOL|BOOLEAN|BYTE|CALLBACK|CCHAR|CHAR|COLORREF|CONST|DWORD|DWORDLONG|DWORD_PTR|DWORD32|DWORD64|FLOAT|HACCEL|HALF_PTR|HANDLE|HBITMAP|HBRUSH|HCOLORSPACE|HCONV|HCONVLIST|HCURSOR|HDC|HDDEDATA|HDESK|HDROP|HDWP|HENHMETAFILE|HFILE|HFONT|HGDIOBJ|HGLOBAL|HHOOK|HICON|HINSTANCE|HKEY|HKL|HLOCAL|HMENU|HMETAFILE|HMODULE|HMONITOR|HPALETTE|HPEN|HRESULT|HRGN|HRSRC|HSZ|HWINSTA|HWND|INT|INT_PTR|INT8|INT16|INT32|INT64|LANGID|LCID|LCTYPE|LGRPID|LONG|LONGLONG|LONG_PTR|LONG32|LONG64|LPARAM|LPBOOL|LPBYTE|LPCOLORREF|LPCSTR|LPCTSTR|LPCVOID|LPCWSTR|LPDWORD|LPHANDLE|LPINT|LPLONG|LPSTR|LPTSTR|LPVOID|LPWORD|LPWSTR|LRESULT|PBOOL|PBOOLEAN|PBYTE|PCHAR|PCSTR|PCTSTR|PCWSTR|PDWORD|PDWORDLONG|PDWORD_PTR|PDWORD32|PDWORD64|PFLOAT|PHALF_PTR|PHANDLE|PHKEY|PINT|PINT_PTR|PINT8|PINT16|PINT32|PINT64|PLCID|PLONG|PLONGLONG|PLONG_PTR|PLONG32|PLONG64|POINTER_32|POINTER_64|POINTER_SIGNED|POINTER_UNSIGNED|PSHORT|PSIZE_T|PSSIZE_T|PSTR|PTBYTE|PTCHAR|PTSTR|PUCHAR|PUHALF_PTR|PUINT|PUINT_PTR|PUINT8|PUINT16|PUINT32|PUINT64|PULONG|PULONGLONG|PULONG_PTR|PULONG32|PULONG64|PUSHORT|PVOID|PWCHAR|PWORD|PWSTR|QWORD|SC_HANDLE|SC_LOCK|SERVICE_STATUS_HANDLE|SHORT|SIZE_T|SSIZE_T|TBYTE|TCHAR|UCHAR|UHALF_PTR|UINT|UINT_PTR|UINT8|UINT16|UINT32|UINT64|ULONG|ULONGLONG|ULONG_PTR|ULONG32|ULONG64|UNICODE_STRING|USHORT|USN|VOID|WCHAR|WINAPI|WORD|WPARAM8#x=b6$MJڕr6ڪm5m-GKI̚"U {v/x_7H"HJv%&H  $ ܪ~=O-3tX\,]bl};Ҏ%lݮ0;aXE?͠~t]X:EB($0<Xn*(z aOw0,Xviٱ݆adfwYb,閹z-#a:cK]e-LT!:OK&cf(g%"X~dϥ& Q}>5Y =&d2;9[^̞6Mw*yyL!up!- Zb3=p3;VovkWYh)ږc MhmLd'3eehCB΍?$,Yat\FH{RR p&.J7=w.%& 5]NMrJb2lnBA=>!]r۫,|$|KRf)o$#-Z S}|CDr|G{5Y%jP<ͬ^  ;@`^by,|.9<&xo+ c. >:;xlbJ&UƸg+K-'klhA59~o7$hmqY<#A+R/9(XKF{ mua9pxCRa<1d7%~ B4wZTזg %&OeHڙA& F YbSK!AVLrI^"T@6%9Nx"K꒙BBƅ1Q-It(R5<5@tgb&mXjb\*IͫY"tNSರ4Pz,|+`/Yx\W'YK60~?9Ў"Dֶ(㎡d;j' |ճiMPK=j-׈T/YlR8j(H;;i Hhx~ݑŁǒ4X/?aIxȟ&64xgy 1٥č_J󠋏 %.*}'Spo63'3 X+3+\9z pYLWMtQfI9|zn0WڣN7 pSԛF(%FtV$ $.f,|Hxd\m+'L/|.;!f`Hg_f)ILJ՛58iAAXմZCӏ `2ZD/3<219"DbT0WL1C%`t(x,88JZkA#%), ZmBצ k! a :,fzU"wn/oe\["t Ӹ`9 @xxBX*5ZZpҾۂ$cRh32aJbȄutKHA1;~XH/›qoO^.ߩo.K0QkG݄E BKdw ^y44 q'C"z4$e ̤ne,ۋX#;d7o:LD95iU+;][]٘j&ju=e^P4 ^P"Rss{bJGCPDQVay GCeZHpݭ1K܍iL-C u.+PkL[bOT*ݗpT*Q~ZH%ڹ4r9^O ̮&Uj+.y$)ww5laM^2A-8``V ?iZ4H3rDB= $z-Dl\/wbw_XV9$r\`Ch9xl8vb >ݡoLcRGi% i` 7%SsH_s"Oz2^?V1;0zpw)+񹮒1[m=M~wW-Y"3A$Km50 2}φ]^U6֋SS$3tsWST~xdAa9 [%(eu@4金zhfKI!L@z?U M]=߬_12"U cYE5a(la ջaZF, Ro}JJ{8X5'A1qע3L_$)X|ʖ9ANG-$3cw)`C oUBR W_D<¸=K<+#0پ!e%|&/7m'L Lzk<O8o7c\K*;9E!M((y|_h\I!džhpW+I-]~Aԓz-٧`'jNr51_Bw:)\w`T"OMɓ^ RMx"3Br=-U5 =זC}2:D(dZF縡PA2a* ow7'҅$[B?BN߈qZw<=ʋД#1crb'F& ا2Q^Rjܧ>K=t\ڵv!MKKL烥h Z'n RF +ːgh8ȁH RHJ2k$[%$ttW$[%G3DZ< &nG"_Y22xn4<Q 9l^M6{3M@; G=">1@Od"PH`\C"fUo^F8q=pfn))R^08%CCBJCbe?Ff 1SI&G5NH+ʹW yMhU%/$=mgIpK&HEŴ%bIχz!Rኆ [ ČnIU\ziOXr#?MwsZ5i8mf9=PRA5 d6udA&׎6jX <9Mu{:jj~ArmEсY]cP_5 tAk-R$2= bk"tXm#h_t{hчnӰM:0bi]45S3ıt;b䗐Idn{/W@i 4L!e֑< ë855DxZs uD,B4{H"yz oDL9*f‘Etn`ݞj[IDĵg4M3 fM0cldAe H7Wpa= 71+]GC6(@MݎDcs]4t&ڥ9l>d4>``.j$eXny`j-k _8jۨMځsaD;͘X`>ᭉSZ3VigH=*uF-6%7qci5rL$( u ݗ>^ 4Ԃ#s ^5m& 큆hY]rƶSpq yB6ԻM ij6"m@:M2iA 7`5Է 7Cꭖ9䁔b1p=xC#0S#DNi M PQ@(9LAjD E;D4O,J#Ɛ["iթⅨ"ԅv5лh{ \2ia 6OH nh.u20&aP TEp7oAW+Asqbkr.UɌ&?Iv,hi] G0bAq 2-h0 (ЈU e1j \ |&̰>G3bVcciXl_AwTmbib SWk{*k)j\ŏ"mmˇ\〭2.\^bEB#hmb`5ux?HC3 {%=/,IfPs4 ;O:0I#IK:9Ŀ^GKr׭TtyuN`ۣposnK3kK䡼g lO?ߨk"Oȸj8x" ߋu9C[KL"Y T6N9큸 "@UnZ[E8%1+'W7;[ÁvⒹ)V[.$:F0ld+1դ5R]6e HA9gNLh 6X4Hׇ w-njÐ.ڨ^!#"߱v6-sB`Ss'XPDϕu{2'Ҏ!%Mb`ƶ6֔&"EE"~)7_[":o RPa؁Ql͸?gT#/œ\><(Kȉ%V*F>$b lMsjjUT—i,~ICO߳JjdomHbA7M)`揇T^^ϼsz1D( f#T,.seF MdaPۙ]iE}V)gP7 Eꠄ-EL)R+d1UN>a(d[EFj@RqqI,ѤO[G@6[c܆N L9.+؆ '9KΛQlļ'ֶg1xs0peiو>14"&VM&3-7 #4'lrUOE1|;NÔʴ@;JOWB>*}FPwBw^H(R#ˏ'84!|U“k-I%k+B/51Kum16*TGVtt ^A Uۀ@_ L[ƎS7k:h={Zʑ_jOƨ2Rﻪb٣)%uQR̄2DX HEeh}r)CMBF5I_HX z3 OR*"j$T1DK484q !m,_涬[ێr(K qd_^!jQzNld-_K/D|LfY10Ϝ~kj*W"ցua$315iN՞Z$9 hˇ;>Tg6kx i7iW7&s6E=f 6A#K+x)~0+~p6SwN/7pyZxJ1٫OeG{ؗ IT+S l@?DGG3<}ʣv#)t@34NU|g!|_%YK:J }` } `YPMz FV+\|ʱ`?$>ߤ0F';X?bɷiiGl'PuQuZ{_%_pܺUZm7UrˉTW )̳iQq-S};ԣg=\$F1EƑc?&fq#ZIV!^rVLNqjvP-vSp>3^UazLㅜPH!+( (z[rܞyT2$5F N۷O짹&}C;!ROZŞO :Ϟ%0DɽL%!v~MVN)_dz."='i,Wde6GNjz6:ˆzyVPČI8dI$w ϱN.V^fȱC&~  <~ž#qUu!NVS`kDZ'`;%$6,dz@wrdLp P( ]_}4LU?*eERQ}n>En˵Z7fǦ6 \{y tq?MtT/Ste goوᧆ8 ![IFxA5Y{K;>޶L5^Qkɷ7&rsr}#gِ/ڴXMVTw2%mǻ0(l@ܴ|4wsm"F6&ڶ6ڄMeH7k5Gw?]sucz:@bQyot|i?9k<'JʨQUҎes^lO @54BoѾR5EWb-u|n/$l5vg$cSg΅+p}[#Wݮ;-:Ʊœ5 ,>>#7}/@d4 SƏ1 ,“-0}IR.LRi£hGH;۔ ,qZ$0 Ieg.Y^8_r$A$m9'>|x~@R]]@ _^ˋPLueur4̭~וe1{.sK ͭM./A볋 idayr.|IK> ./T-U* 06l2VVe(l:~X]TW?G 7H.͢E@ /PpXV^83YeT578++A@j\JXp3UfX2]Ai+sBˬ4,+2 ,FEϊeV"DBURUbUrU %c[!uHPp>W+ upV 9[ Y[a0V!JG[!vulX] +su75ʚ5ʔ*}FU IՐ*%ʨ2*%Tr*ÐTh-L|VV>Y|UW÷#KɤTR"q!m4F#EɊ4qKиg1f+8!ũDh !i7Y)V7o,ҭ ߵ_ß[6i8^`ˌ -C-x8'7 /ec5 {*|Ѹls k+x6{G.XI aa& yO40"gfCxIЂ] .E’~*j`}ױlb u/ΰIq$+D3L,QҥLn bs Y]$+58f"#f#bVfo\e;fOEnWw \b*-J:f<)\&{fM$\ђ  [Q,Cʠؓ0Vum"+HvI:';ibvԱTG#[?įCSScsscss.erb css.liquid source.cssabsolute_lengths(?i:cm|mm|q|in|pt|pc|px|fr) angle_units(?i:deg|grad|rad|turn) combinators(?:>{1,3}|[~+])counter_styles#(?xi: arabic-indic | armenian | bengali | cambodian | circle | cjk-decimal | cjk-earthly-branch | cjk-heavenly-stem | decimal-leading-zero | decimal | devanagari | disclosure-closed | disclosure-open | disc | ethiopic-numeric | georgian | gujarati | gurmukhi | hebrew | hiragana-iroha | hiragana | japanese-formal | japanese-informal | kannada | katakana-iroha | katakana | khmer | korean-hangul-formal | korean-hanja-formal | korean-hanja-informal | lao | lower-alpha | lower-armenian | lower-greek | lower-latin | lower-roman | malayalam | mongolian | myanmar | oriya | persian | simp-chinese-formal | simp-chinese-informal | square | tamil | telugu | thai | tibetan | trad-chinese-formal | trad-chinese-informal | upper-alpha | upper-armenian | upper-latin | upper-roman )custom_element_chars`(?x: [-_a-z0-9\x{00B7}] | \\\. | [\x{00C0}-\x{00D6}] | [\x{00D8}-\x{00F6}] | [\x{00F8}-\x{02FF}] | [\x{0300}-\x{037D}] | [\x{037F}-\x{1FFF}] | [\x{200C}-\x{200D}] | [\x{203F}-\x{2040}] | [\x{2070}-\x{218F}] | [\x{2C00}-\x{2FEF}] | [\x{3001}-\x{D7FF}] | [\x{F900}-\x{FDCF}] | [\x{FDF0}-\x{FFFD}] | [\x{10000}-\x{EFFFF}] )duration_units (?i:s|ms)escape(?:{{unicode}}|\\[^\n\f\h])exponent(?:[eE]{{integer}})floatK(?x: [-+]? \d* (\.) \d+ {{exponent}}? | [-+]? \d+ {{exponent}} )font_relative_lengths(?i:em|ex|ch|rem)frequency_units (?i:Hz|kHz)ident*(?:--{{nmchar}}+|-?{{nmstart}}{{nmchar}}*)integer (?:[-+]?\d+)nmchar"(?:[[-\w]{{nonascii}}]|{{escape}})nmstart&(?:[[_a-zA-Z]{{nonascii}}]|{{escape}})nonascii$[\p{L}\p{M}\p{S}\p{N}&&[[:^ascii:]]]resolution_units(?i:dpi|dpcm|dppx)unicode\\\h{1,6}[ \t\n\f]?viewport_percentage_lengths(?i:vw|vh|vmin|vmax)5x}GuJ>m1>|#ײe|1k-L1IݵS33ڞV;R&7$$9HH#\! A ?}{f՚g_]zwI^)w(g4Xn(S7ǡ_ϛ&,GVo~D>WȨ3Aul~?3w|^g$NhtS6ٰ/ =0J>_aZz:5t_TT԰ `[lHH~d&fQ)JW(P 7XyvԦ4` -5oWWEA&2͵Ixu[ڦm ^6Ń*kۦxJq[MJqmGTCmS_:U/fwM5j]5wf׼l+pm)}5}_+pM׋xƽ[ie`.):fxR,a9v Su?^s2kY^rffff~P>H P`6m Mm Myc%E0L nkk#JYm ]FT񕁌Pe|Va @XImC`4MhPBjֲS0 Sd&7yшmנh+Y$?Zo$‹^Ȳvh]qh)NLh-xh-ζ"m@ZZNjHí5.iDv{hh6К?!CU|Vݙ" kh3au?[ lN\$&lXS-<a7ۜ)-vV)?lZANUjr/3f5šm)?=+Pp 1)xB>ժi?Az _ʧZRQSqxҐ*ͧ,_Yuj ꯭&up7VٞOy}3 qj3Z,UZb#A>94@|TUWP_OchUPYeܗ,^teĨu-~jiг:Wl~͢Fe6ޒS5<*%+TUcvf 41D쟬N, UVf¬y,Bt]y"j,xGRᢁo{YwW(Y;Sy_CﮩwX\Q%U᪥6D_c{YE-0>wumռϋVnS/Ԡ\Bi\$M()QCWCYNU~m"_Yv۵fѺKnHm`vBZ\:-I&p6nX:a3Gߘ!Mw@VqV.[]u׶Wג3o; ގ8{=ǟcY/Ï>tvZWs\w:GB>ot?w;Zc0u rthn CTR߸4e܌xQu!A9+qmu SMf?]~hOqʷgNgN { d% glJH[\^jsm2&%]*#n_F\v2 ן!;OR\2?+Kqqt"pd.moR/_kw |}>M8]^ڥ{~f8Av)^7?ymZw _ۥ8:rrz)d"_ٹ*ocnrTv(2 Eje½W]ڥE_v g ^Z(t bbMF-+#`pGrl24v.+򸴫iVm`]nTm^߭jC; W(W,Vn&?2?[+ykqㄧ`n~?mOy/3?] ꭳC=o!/x8uD^Ѓ(`~4`aucO;z~/PpvS2rM?,G'<4ʼ<ElR, 2L.Qȫ#C0h<5Q?N$8(!~7(0HA,an?NEEuL8cEra"08 8YY4(22$kUB|x K&T|+*"oR~K(+Ș6A/ҕis`Ѵ|! nِKQ!ܐ[@)9>T;ʜ<<H88?Y wMK 1U MF&#{HǦK˥> eD- 91H}s6r";QVO/ZȀoy9UݺbT8thyEݺRqwλv+9?pպ ˇYȇ[)yդ/*D/)@wwO^jҳ; ߅6|!~\qh9ivK-Nk+}t}-oPyWRSͥ3oj 1iɍǭ\L ؍aI4i #H&r SK12lX a$¢|/tltW9a)[ on!8E% oj֞XJnlyWBr"(3I z:BjDVk ݢ O b!Q 5PycNT)V6<[)Kt*@ K@cg[AfFͯf"$&JJ^Z](<|3󼬲CDY}q%G1"$fC:6"ET*BHdƔY!>g]tqW,re7ن:n*;$4! oߔ%F'.J%B]a!I-S8 a$a:L9@?_TM,ΠJ,bIG1[Fp߷xɻy 86q !o 3f2./3$)0ZWG, ^F<.zC|-1āEº, M K:۹: #0 @YR!"[,25v rX(]FVh&/:66#'KIM$++)Lp>c$1ȩd˚M,d$#đVa\R QLC[{~9М!'aPq)Iož" x4&uZFVe*G!~D!dpi@Ldd`J\TӨ=*V2<"u9$@SHB"8hXqT diQTi+A+VwO,AŀH+. P_.)ZVZ5Adzx.kRQ E\ 3fC@(ث*1Зʊ8C5$]^貸j;Gqr3;}Ld֯A z/;̧@zѣn,N뢬6BFFۄ$K H/< @o L!Z_9}|Ȕ}(} -4kn2 { &nXrw4tJ{v7Z5/Mآ#*;'<&(ޢx{ 6P@tb  oܯL{Vw; qXa;/Ѽf:r/3`vkufvW( \3o׋$WVUGu6#)JO=Rੇ$B?pOU>A?S<;!=*<}B eC[Q;]l $-fSY Q„Aq)&D!j @Qdb6ZxA̻'f ]4Y{/V Ff`( FKf/>T4بlUC=` ׏pX= 涐l=b?0LJLV]h~~݇aK`CQ!Q4h(UL8nt=P?!B+B0 iФC BAYQ,jPDY8 .NrQXX8u Tn_~ H8( [\sk}j.H 7bv]A+6b u{b1aa]>9&Jє</BS]X*oSmC= ͕i> 0 S l!̑},"oE:[*0'c(i<#A,SP'L sLmdFN; > EM}҉Vj֖;{zZhU @4Iרg*աۙdM]W)ܹG&wI- z_л`VM=v~!04lN9_CfWnh*\m=fX7_[ٍ~jiƼHMsFenw5gScE3椫<̟k}qܑ;sV|BOir'IY 6-uiC«hS@A=:w<Otms`m"дS'K\k6nv.mS7w}>+1**իAp, ZlZ0SAC,~ ȭZ3.96(SQWmZzq@Ġr{Tg;+…3\)Hwf1>#fZ)'pFZn:_~?,;VC ,&ɆjirC|D+JJm-nmJ*$0UkS˺R+'YM ۬Y֓KMBﵹUm2JRصeaa ŖukוeA`'N'Ó4!^AEZV`8!c^ge^)BWŖyRe P̳ keߵ uO5yp;M"Hi*O-J7"@]t~L-&a~-YTẔXW뛄u |i8W0'zjIT8VcVSyRo ׶;OOe 4GpY[B!:WPpqI"D9X\O([5ƭ4d͍zCc(& b)n|Ҫ[[` XX-*X4;Cgm>m~6}5^]#}w={[a٧OWLۂOc >agUi`=w-9`l[Dv78^]ǓmOw>X,TON^LZbas\Lu>^gRm9w,KKugz Ki K]Erw8طYK'ݔyJ lg`wM`OqrNkz²>t߬\>a! ZYQ862=( ;VIUK71]K ؅4)t4*қZgoع@z\bZN뒆4/ϝ즡ntaR\%@;"!s]HH,fZ΢(~ p'  _xhquY|fxJ@tQHKl'[ V!yN.ޅfP.`Nk>qoq G*KX&&cIT@\NNgQ (k|G{[#&/jwUB''vi"|qe|=F6Mk玦/awJM=8ZMH;p+4WP Onx(X!~8{Fw^.޳IF9ސQZ\Ntm#t_G"7 p:U_xȳt\KaAEt#Et"oEnyEx]iAJZ|M,q\?݁d2.NT ˜! '{I7P|k"?n FcT S\Ʌ` /! jBJ 0x+A;#'x-pƕ5\.Y܆@d" Ҟ)/&#  Sr2K2YYPg_Oc:]Y ,S(dkvbp#+REr"&t /c,("^+`CYf@c/5\xr9@8t崪- OXTA}?H$}Q%o~*Y!Kj"5\f\h Wq#F9^ x١:O b(ҹ<" Ni Tܵ ɝf )H1M3YPLHua(xe@5qKan3< )3hUR!HRW5)r8; ٸcn[]6Xdr$t?(q pn@sfBGTLS>w{ί||y#I7nMP1unR%qRH's1GH$ne+ʵRGH y{|GpGɅAtriM"r%PӘ @5:+r'irHrKmQYЄK 0;L$grc- ys,{ՎFLX=U? FIɁYa=wpf@bxg:&!1 I -%;P)G9~T@+ %22#t*BȠr*g~J;SGI'BH*QUȇ7}jwAA=t] T`}FǏ e8(>@w w ]3Ğ`>kr %7ϗᦺ]]ێMolz|O(nuV9LsYL0[RMre̦s9~+:c-MXk{K@O K m$j{I%Kw۾;4l/M@m̭"^aaM*DM.;G+W'JóoXދb:4DUHK-{#{EJ&Ǘ)6 QeuXpDE:t#O;}Xɀ1nr'WbZnsS/@;m2M2nE#ZPR39GvDV-se$vYiK1qJS͢Y)zC*Z-K vc"Q;6q93cu ?hXEj}5^ԑg WUʗZUۧTrQ[k],m Z/._]S1srwZy?Clojurecljcljccljsednsource.clojureatom[^{{non_symbol_chars}}]+constant,(?:nil|true|false)(?=[{{non_symbol_chars}}]) dec_integer+({{sign}})\d+(N?)(?=[{{non_number_chars}}]) evil_octal%[-+]?0\d+N?(?=[{{non_symbol_chars}}])exponent(?:[eE]{{sign}}\d+)floatX({{sign}})\d+(?:(?:(\.)\d+{{exponent}}?|{{exponent}})(M)?|(M))(?=[{{non_number_chars}}]) hex_integer2({{sign}})(0[Xx])\h+(N?)(?=[{{non_number_chars}}])keyword5(:):?[^:{{non_symbol_chars}}][^{{non_symbol_chars}}]*non_char_chars{{non_symbol_chars}}#'non_number_chars{{non_symbol_chars}}#'non_symbol_chars\s,;\(\)\[\]{}\"`~@\^\\non_symbol_start_chars{{non_symbol_chars}}\d#': other_integerH({{sign}})((?:[2-9]|[1-9]\d+)[Rr])[0-9A-Za-z]+(?=[{{non_number_chars}}])rational-({{sign}})\d+(/)\d+(?=[{{non_number_chars}}])sign[-+]?symbol;(?:/|[^{{non_symbol_start_chars}}][^{{non_symbol_chars}}]*)xZ[SF@n8W48 VK8}h;әyT2d6JroJJծ$a=߹a΍?me?tvsGHP֢:E9 |B9Ie 3au ,\^p:0Io%[@wݶ0˓]ɖ )B֋ѺDm $NQX6ra"q{)ЄhyАme@ΰ xf%9Fq@mWu-C|5Z9ClZqvyC fp'=!@"^f|K<ɠϻVV& 2[WڐAEDiNwEwW1Zg<&s=Y=c,z˟93>+%: 짖eTa:6@#KFCP=*8aݴ9p̢"F @`O ,Oi@i_oHdR?.0Ht푣XfV\98&=KiZ2}A LJؘ>{55ISΗCm7ڎuSvȑ@/RM`;y[.#:iTIƁOj+.DL˳Mfr=CV:ݞfJhrPhh)[ `Y7f7[ !S$=.Tg1{َ2crAq'YofqxD"@ޫ;jlvVkl^}}[N W +ՒZmu%b.T2Qq>V؛yL*6$Ty֌wRVZcn9f X"av7%6acXt-jhڼDIغg$ˊw. ".ޘ폴m'Y}X ,Tg, YLg#V_v|CIRw:-ItJIix au]\ r:ɸX 6h/M~Qϝa1m|$5x 6c&w[v-PkAՖx$5_s]3Q,Xxz!@VyiH3vKHw*SCI(ьf+p[yQG$ӹ.G Aı\ӄ>-PFI7HV5p H'FE)ϓ] #5cL"5;wp9-NxJ$$DsoN}>Hk/N=~RÔ*`bLqnR,t>#BEU0 AYBEH ܌$f3 q,\xW1 M_y@YhATBfS_#BF唻S7 Mlp6 BL2 ^&/Qp2wjPmDkҞ{ '2MŎ.5;[B\p`tk.Cr!F6^8 ˱l۝?AhwԯL ۹(H"5M!ff5ЪSz\\KeBfPCW< ^P7eY- `cy\O7F<)0(THQ#a q0H!|N8s ! |<\y( QyFz<cr|>Aq$P(8[ LQ>Zc`%ԏ (uP!o͹*],R"aQFPD!Ф!?d04~,i}?žlz ~p[~RK~Ѫy=~CC,{ 5Wϭ}-UQlMwCA\ofKṶXªX{r&Mh,_2&9C* zX&дZ)CF4v0{mG4 U_eBiiΞ|cY~3w~ aјS- 뷍ePr܌'k0 K6{F[(H#JBu)&R}KEsF>T!,zjxDvVE'Fstk"S>7xY Nj5ΟZsSlVK~W!s&{nrhT%b Z͝. n${n 9ٓ2HP62NUo .c['ooP=ÞC!dP+\Ob]ds5te1 >ktAR ~4MR 80:eh7TIƨms*ZdH/ISJP @V[ 9+J[ P  _za$`fc ve"=YxT 俌ُonccj[֊_C VhFs`z1>l+/$"ub3 ~-K3z ʯW8P} S"wWdt so@u۳p!XVvl蕭lo,C2EYiS)pAmϊ-ɵ*sr Gьޥu[82w> ,.h]pzwet)C5=}vVT^N2󀧛Μ>O>@,gK ח? 6Ծ0el/x Pp cB,QՇʯ>!&ZL0 @{;U5`??1}qDvtPRsvڤZ*¶6+rJ"J@ڀׅ sƮONxÏNNZ .Y4"jӌc>x{!ODa|%@LjԹEi4B>` DB)Nh+3Y^^2Ȟsdۡ#7rOrǭp,p|}   t<1}Ԣiʷ#h/\΁S+y]vt,nٱ PRY+zƞt}_&!K!K7iԂDӆo P,6@#e)lim mSSGqDu)C\a{I \ AfbҕK:ҽ/䳣C|\]rJՑcN&I[o`IZ8GHëA=} ^wjˉQ"Sx9(}kc*0 㶰{_)6K(qE-aMZQi*i-/Mk?Dհ0&uǻܬe)[ZDvԅݷq(QM} QbW (cB#kMsF|=W8-T 탎E8}N'm!E4d>ۢ WKcm mcؾte ;t'HjԼnQ&"i]m( Vԥ)Avz۸{}25b xvՄ*AHT*vjXK@ѿ.P[%I̒₞nlsy7=[4iP-POz!ql0d)SVe܆1[w8c  5> 7"'_w1a. mƮגw% 5DnƀWkF6~H۔.sǕJ7? /NoucUoO<Z|eZ\v[-co{h9|HJSa[y *տE3Yr59gdTK=?B}T9SmJyۨ 7?ypV i_OJ HOCmh[ö^JHUEx;FqF=qʈOJ/ plMDFBe_砱MzBw }S{F> ˝~@hiݖˎ+rYF0D4X'T|Cx$y4B!;khX7W&̵ϙv`Q{rY̹ D?cP+L2 fs-2*oe3{p{泵[2Y__L//[X*LnGhZzk.6łl e(=]_MO/mUJ8 %L;A z0N`J uRDBثEzT##]ϊޝR ˅]ܚ>_KY6מ.2n[jvVK5! pLW.kv=Y- Awi;] ww/s?Ӵ$4uKK#Iť}+.2"$m^$E'O|)6;V/NUvD=Czh18I'DYlս )N#8P}Y% O~E$ UƴtToQ^8le/NV}!ϭPpRx0v<0~Yl",]D6Xʤ0D2R(NDDUk Y;.kN m' Ҝӣ>}󁜚dP==zr`d@$bPԩ"qbfw;I8$eL3@Z͋BH#8wYV-\ޘ.&q*NRnv'8 JuF@OzP/x4 j79_i(kifoӐ|4I6a*^L~T,Hh78qm6y29^m\._dC܉*-ʞ2*} AeLwZeߪ+*5ӈfHGthZru(DJly {γgS#Hwr䩱䅱ɳGϿ&=~ة cO9M}/ӎ=A8s>ϟzx9wg8}s<{=E 3U׺n)Şc`M[$,^ ~pK4ᅿG+<{NQnv&ۄ4$]9&iTepxBkeph7tH 'ҝ[ɍ]MDF$^ piޒ-а+1m{wfsemව4]մA| \4VJj!,PF" )/(Hɗ *PSN%~[VE˺tL프A$QY~lj.ftt,JÀ ljMBlYE+orX[DuLrDDAFms]|l(94~P5+JGƻtkm"ULR4i.oBv/^mkWBNzsp0QM?=$@9["$+uÎ+wS, VN(_1#n D* }U$^Ъ Fػ\<+qh`hPmAuV96o߾\~&/JBO7¤ MQqM7[Oٯn⛪z-JC8ڌ٩ 4h cXZ 5J*&'iETRu* 6}} 1hEOǡ U2=s`. 0wY5ZanM~U\b1puE[C/)gsE7euӶnr̆7\43T?T}ŕ[%8)EM^E|F{q[  w!^@ZZQݿ3@ԫ`fI '8:ʨz\\3=Z6cwFI,6v+^W7f<:il^ J*r Eqxq*wn[8G4;!_._Hszi0a~q $S5tA慮X DhM1>!ro:êN9TNQKQ.߸C1CxBOJ@(>`coP9|=U5ҳ!f?Bٚ`ƒ'wÏN}(˓ % BqBeN:961Z͙BUmVlKbs~(C45ofwځׯx'W>8KƟ0EH>x `/pc{N)꽌1<&R=觶VWU`ke#Zy$<:z=d>`9L5;ZC݀ @Ż[*2|i:Z;'"鐛~X$K!d [b3K¨>{)B^=FD}0vۛ5L;ukK?:Lp,/Xluܽwi1 -C};ҏi)44z\sJU&#Cw~oG6N'@Z,Fr 0Ձz}K_UotMF0ކDHg„v Y()`;r@^5a $\-06)C [T/K `MA%a8 HxĜ+*U+,du6  3cZW~JB8b[)ݒ(u/?uu;pVx8"NjN@fl̝vQcXcmڧev%oHީ+ާ֨x:vA>ԼI.x_/NJm~;qSԗcs'F(+12!1LSbe35]_P`zŽʟ DV-)~̩Ig<`&R!ST2_e畤m76e.OAHG5J'^_S siR'pmxi~M74t%=fH.QwWcWCƜ7_E~1ԏS BvbuY/wNĨsyz9S|[fh10V TYEgzLi25f'N˧΅ D>~HW14䖩~$ 9Da$c]r "ʶ0HmtX~ICh3*Qh\QY9ELa*~I*ϣi Uբk뀬\ovz7 "=>v T'c@%Hj-b>RAC},.j9+vkb ΎaVk4D3@lX,*XecdEO5=ik= rWR'X?aÿ\.t{6BEDm]J@ ?.# Rr5,DFuX5A(Sf΁(FEkՊVś1;vjekI/8#K5zybeY,͎{Z7*~M,mi_hO#hx]wUSVd5LH[g`]'Y!hCbUhcAߓRwbFZʊ(m o[,BRkؓ̏Q,an 8̟i! *pIg;76w*K54L┿p8%,ɨ Wš  B`+ e[]cF߻A##'JxfG,ΥxaMb/\Jϩ718TGC N Dad;t^&ӦC҃}5nvlr9/lg4~ajݻG;cjhuw;6o6qMEdhuǑ/,V~6=XےwV)mkktW):t JD n`jccbg(8՘%YmK莾ZB6W[- ղ;cGպ\Oխ\D\͹IY*n3 7.̋$m1.ȳ G-SxVl"OY<-MhEF)EOc3L n$NRj3sPEuw^Hva0dvP3i3l̰Ȍުyӗn̊<D8ZAe:yv& t(vILj_fy">ʌ{t&6ٴzVƬ:U$cr䩱䅱ɳGϿ&=~ة cO9M}/ӎ=A8s>ϟzx9wg8}s<{=KILkY:پՙsAVPS_H* D^߇vBè>AGȿ#vQ"?Ō B#AC?H9I)xH S轑9z!R @QD14rḿsJrQt 4@$.C]%gyp%ע^Wq̶d[Cx2#we}a.--AZETSZrf^-b8ՐĶ5) McA6؃YܨFu^͹qu$ϏDuuc('4_b+EUOEkETռ0v?ra\a:Վ %߾T"3ڹƭ`3a0醼76A Ь@wTjWx"hmJ/ϥv˜X ZZvI ߞ֘r~:R=_mKҝi΅oX'>4ZMB}\ m4&_Lc+OO}:r;Um~ƂVk5'm?aӈBϞMW8<;)GZɗt,ԕ=+-\ ZɊ&t zQM%n/^%EG~b1K I*qxn#|2Űg5'TE*xF!d-qd4NȮd!P8E^7A Z@njiGzeth͖qgF;;4CmdT8[stQjRPE<- '3~.b=t:ޯN0F6L/ *XVb9`b"'Eޕ hE$t9@-OiGn (bl6 #ݞ՚yۆPp.A|p*(8|jA[x9Bal|, y>E?0AU87:I{bej!?ֽ!1 4^=T(ιu?,vGC'Nut\  *(MM{R 63]D/OŕѻX4?y龶w‰7th& ʹboIj*1cDY($7 D3E ]7>@iu@m]LWLt?ll4&uɯ2zu] ” wHpkԉ8" x-G$]K-@; /14YыPG*bºDiܒ\q'q-3V+]C:2ʼ?/p7!J![*Ү-B Nc2!Kz -c>HxKpj_%&B.@qDo׭}h9iR ;A| #0rl\Ң8 iQ&8^'y5iT΂) ܧ < ʛW <#Tۿ,e2m&Ey'1%C69D0_4ș-_@==䕩Բ!pKGq^DS DMD Outputsource.build_output.dmdxڥ=O0ԁ ]JLc@Ǔ¥ Vu{';cqN^i\q: 8ČdOGe5љ`u][e۷1zqc{OrYEXtE)YN!z$0ڧQԱ18zixntى:Ʈe_Ek<{]NSk[8l|P^e7~"]\Diffdiffpatch source.diff(?x)^ (===\ modified\ file |==== \s* // .+ \s - \s .+ \s+ ==== |Index:[ ] |---\ [^%] |\*\*\*.*\d{4}\s*$ |\d+(,\d+)* (a|d|c) \d+(,\d+)* $ |diff\ --git[ ] )xڭ[o0ӋZ * ӆd$b41WC$8UM;ر&mir:0gm=2g\kS&=.\}.FS{Ghyeg~DMjR^sϸZϗOp_^~y̢sҙpJ@ 9)^8)EF=!Fb8 IZ] >?h< 7j~?'AvhD(ԴZdnnZJ)>#|FI@k* V {5 =;$)sBBM7֙wf.1e=:E8_^r-z-L"vrCփV9wŔBktVΆ} J/ qj_(] v F~$쀄XTVK0hܽ;^!C%UGRuh; f=^tdqlBt`Q TnܖSwuNCh&.~>ߜdt,.4VUxwʼn7-xU2  ^n#/x+-y>Fʒ>E}.qvH1_.Gz;,r(6\u]gErlangerlhrl Emakefile emakefileescript source.erlang(?x: ^ \#! .* \b(erlang|escript)\b | # shebang ^ \s* \%+ \s* -\*- .*? \b[Ee]rlang\b .*? -\*- # editorconfig ) atom_unquoted[a-z]{{ident_char}}*erlang_functions(?x: abs|adler32|adler32_combine|append_element|apply|atom_to_binary|atom_to_list| binary_part|binary_to_atom|binary_to_existing_atom|binary_to_float| binary_to_integer|binary_to_list|binary_to_term|bit_size|bitstring_to_list| bump_reductions|byte_size|cancel_timer|ceil|check_old_code|check_process_code| convert_time_unit|crc32|crc32_combine|date|decode_packet|delete_element| delete_module|demonitor|disconnect_node|display|dist_ctrl_get_data| dist_ctrl_get_data_notification|dist_ctrl_input_handler|dist_ctrl_put_data| element|erase|error|exit|external_size|float|float_to_binary|float_to_list| floor|fun_info|fun_to_list|function_exported|garbage_collect|get|get_cookie| get_keys|get_stacktrace|group_leader|halt|hd|hibernate|insert_element| integer_to_binary|integer_to_list|iolist_size|iolist_to_binary| iolist_to_iovec|is_alive|is_atom|is_binary|is_bitstring|is_boolean|is_builtin| is_float|is_function|is_integer|is_list|is_map|is_map_key|is_number|is_pid| is_port|is_process_alive|is_record|is_reference|is_tuple|length|link| list_to_atom|list_to_binary|list_to_bitstring|list_to_existing_atom| list_to_float|list_to_integer|list_to_pid|list_to_port|list_to_ref| list_to_tuple|load_module|load_nif|loaded|localtime| localtime_to_universaltime|make_ref|make_tuple|map_get|map_size| match_spec_test|max|md5|md5_final|md5_init|md5_update|memory|min| module_loaded|monitor|monitor_node|monotonic_time|nif_error|node|nodes|now| open_port|phash|phash2|pid_to_list|port_call|port_close|port_command| port_connect|port_control|port_info|port_to_list|ports|pre_loaded| process_display|process_flag|process_info|processes|purge_module|put|raise| read_timer|ref_to_list|register|registered|resume_process|round|self|send| send_after|send_nosuspend|set_cookie|setelement|size|spawn|spawn_link| spawn_monitor|spawn_opt|split_binary|start_timer|statistics|suspend_process| system_flag|system_info|system_monitor|system_profile|system_time| term_to_binary|throw|time|time_offset|timestamp|tl|trace|trace_delivered| trace_info|trace_pattern|trunc|tuple_size|tuple_to_list|unique_integer| universaltime|universaltime_to_localtime|unlink|unregister|whereis|yield ){{ident_break}} erlang_macrosj(?x: MODULE|FUNCTION_NAME|FUNCTION_ARITY|MODULE_STRING| FILE|LINE|MACHINE|OTP_RELEASE ){{ident_break}} erlang_types(?x: # builtin data types # http://erlang.org/doc/reference_manual/typespec.html#the-erlang-type-language any|arity|atom|binary|bitstring|boolean|byte|char|float|fun|function| identifier|integer|iodata|iolist|list|map|maybe_improper_list|mfa|module|nil| no_return|node|none|non_neg_integer|neg_integer|pos_integer|nonempty_list| nonempty_maybe_improper_list|nonempty_improper_list| nonempty_maybe_improper_list|nonempty_string| number|pid|port|record|reference|string|term|timeout|tuple| # erlang library # http://erlang.org/doc/man/erlang.html#data-types dist_handle|ext_binary|iovec|message_queue_data|nif_resource| deprecated_time_unit|timeout|timestamp|time_unit ){{ident_break}}ident-\?{,2}(?:{{ident_unquoted}}|{{ident_quoted}}) ident_break(?={{ident_break_char}})ident_break_char[^{{ident_char}}] ident_char [_A-Za-z\d@] ident_quoted\'((?:\\\\)*\\\'|[^''])*\'ident_unquoted[_A-Za-z]{{ident_char}}* illegal_ident[^\s,:.;'(){}\[\]%=|/]+support_namespaces(?x: # builtin namespace erlang| # erlang otp libraries # https://github.com/erlang/otp asn1|common_test|compiler|crypto|debugger|dialyzer|diameter|edoc|eldap| erl_(docgen|interface)|et|eunit|ftp|hipe|inets|jinterface|kernel| megaco|mnesia|observer|odbc|os_mon|parsetools|public_key|reltool| runtime_tools|sasl|snmp|ssh|ssl|stdlib|syntax_tools|tftp|tools|wx|xmerl ){{ident_break}}variable[_A-Z]{{ident_char}}*+x=uHIDIXAxE$|bYlه#\B N\*Nj'$rzqq^ޜtg,;]|?3O.&(Lhm54Tvqb{\ q q8MҜjt7<<ʐ{V3l{&d ծ.K!x(Ibm 7sXe_nS#*m{44˫޲T;Z˱4[i@Z@(s4;iBvgam)e;0]BM-"ct af=$)vAl)ǶZfs)MNI]*tJЩ{},c`ӊΧ\g`Si)ݟҭI &Ӝ6Ih Z:ڶKrt0#D]:fb%j(˱4#r6)n>b d \'27+xT ܶQ)DTJ V'xv#U7}J'e &,|)2Ȕ7+)Q_tdLVT贒W-:pzz-Ы҆z]SWN6_ ib؟kA}PF:Z0}Y<_%rkT)xV:D <S FomnoKhڗYo%'-߿ClHT`~,'/<1cnIaJ]`w B=9&e`E/x!r@п_`@,fW  "`bC ;V1uSh dFP+h G= #i~"tQvW{B ﰹAS.th/ԐbQ=E(ނ` 0 #!p"oZdl(-XUWk뉋hlib Il*LGU8 A-EޠȮBuBUBL | zn ,eE^CX8] \E (a-%B3_9.jP:j:.Ut,d>]+>ȶ-FP/VAe7+ o( NADx?]+udӕEL)tX*EgulOsW-MT z k$"I3dTE E1䰄i!YGń,KKًxv "nJL6IAsleA#1V`נhEEL"ZU1.S˥1vVm%kt$;lQx (jcj9^CN6)2 LW@.O EqlBm'83g/Tcr .ŨFR8zD[Td< y^)V4+lX;>pIkC;~s]z{ɻӧ*s7hW,,]cOFUAbeX߳K\·L+#aY_nKix5.}̔YH^U0(Q!*p :" b\kpbcֈE9|Gq]KgL]2IMGE.n?EGؚ &5-k/PE)9J{BGD?`Gk::JC(Iyx 3vJ c EK1)øI_}KY2*ݳ i.ẅ#ٰHhׂ`UmY_tSs#ic1EeUoεmKs-H94Q :T\qOG~L_e ؾoi^k"xaC8'$dvV*YgUl2BM//ciĝECݬ,-IKC=% ۟,ńB>fHx=?Y\Z/9O/v;.dzEϮfbobBXd"wuWZzii>$ dbCk'S`G-2HoE/fy41{[EqmG G7W(Rv`{Ma AF~'ުgHj/' ,}%1gUCGGb kP ˍClVAMU;J~il!c=\R`Õ51{S!wM`0!2V8}9G d|$IM'kPh) y XvW F6kX]Z}q4i8רMكB}ӓQ2ugXSZAٔوں $% պU˺iʤ}Ȅ,:]Ro"^!^7G%b|p}1SqلdRzxfR8bGvA5 ^%\0ENdl>>J*J83Nzew+Gw )j`[7IK5/swg$2W*oK/m:G:fA,|6[J?Ė,FjtH_/LA7H9_{JU[1`0bS4ӧM q)C ernyrX۱$3HSc 6*G4IiLGW >*̵D|G>mLЇ$dԔ Ȝз MR=!([ oXs d+Ze?hY+ "Cz S׆|z%ɨ$5Ѱ:d9ڠgcd$.}}zY rزTR_k*-">Y1G}Sqgt#B/pۇ GXR?xse!rY8%Nĺ> ̧u @泮U+\OFt}$#O\w42K<~)șsjY4 /,aEZ,+常Un#"=ޚTgI,P ]uF*:Q Jz&nW24+ RWĝw̮禦Y$#ٴsaRf<m/d%Nd+(:WKz7i_b!7a-x#gԏ ː-' ãfɻHrS c۷4ˮ̂mc}OX3,dVۧ$ 6hjRWRXH@m<ׁ$ɚ#A9D`/D!#~o)(,2X4+Yf6iΝ Sq8)읙YDaG¶Y~ٖ{HmdB?o- XtHu02 %fYf35׻TON:B+(E`qB ("7ĝ޳pWט!Q+SkMqczWRRID??d qGT/T|k-ױVj7 *@f >/T|aCdPwʳWY ¬tP3VGc1f^Jfq{kxdFXz8R8[~W8Ĕ?(Ѵ/.呕}3W6 #CSSyon۩J|߀{> &j:;QXw$^$Y$BR?IFA @'XAp>1A+ig?z})"m}f_֔sҷ?_/\]OP%-3+.Qښ>@N _HfGc7uϷ}] Еi"d*Ojo"RV1ٔqۣ( ;2rmT%r◗]M?`W+US-Gj뒪uIՉ,Tq2a0A< f@kWfJOxΟۚƒJV2k” 'p,GhVH/`bMf(Ndo&Kñ7Y1m3MlG2P8r]/hTy튊JS!7_iJL\IAi9)vmAC:Adq6e ~YHv|xg*EE$@kkc^ԫ-֬*wi%X:o`XFh [,Rmڤ- j긥 ZG-ER%4^(5{VJ)^dMGs E?,{^ם^2^w ͵N|&%;]z \}T|m|lk](Ȓn-gY[QueuHqc&̣-Xkhtu(n7[6>0ٶ]`VW 1! ,ߩq\5}t}] X仫X$^^W:.!ecT^]I!]cqJY!`mҨt'f,k-Wflx7GNΠ˴h6L¾PkS T?@ KsVr^p`< ~?y#G&JAnQokŹ 軾Xq6TNS]p'܍wQ=p7܇Rɓ-s!!9W}e>M!NnU w>W=!ॹ#!^^>#`c:Ao*T/U_iu̹!V3W}"Xs'C0=W}*ӖU/U_i-3!ҝӴ\Lkٛ>i-/U_i-/U_i-Wχ`ZչC0ayE@Ra\bR?OxVMj/)JəXz $f|g߃g.JxOժxS4#ÕRK׃UM/`Lr.k,Y(nK) pGcǝ]y<݁ѓ ^_Dd@g7Q#|V  1ȎkE!cۓe l[35 aь+/~VzSmWDW DqU*Ac'h/39.XAm89.e@K`Xu2> kg\Fˆ*^x* ȳA|DK" b9[z]yA|o2Q0e **k2R0iRܦ T /LLT-.<҅~v.^樬[)$1#g2!j79Es91R5)(u=WJ,o.H`ySyhj Yt$EbfHGzTrEP]v*߰Nr p/lh̩A2O4,u>s?f=KsPcrѶoq/y9kyvWu fס.u&B^韙Dl!URTHa]J-;jO~˾\J{ҕx뭍h!fb>*I ZTRnD^* zrI`UN2%: IGgxaNV0ZL & 7^Isj7ڃP\y^#oH/OS;XeՓ.Fù˃˫qK5vm "TbJ>3X M[nTV# {XG\PN'o* 1%7~Ջ W,ؕ*98!rMHq"uٻ<9ۯ W #L幷Q$W/P=ac!sF _( ?( )ߣel^/d2zpt׾f;Km5MMyY=͜F 'q`^)WAEp`Nld8 kÛ|^AK3`oؐvVh]7@́C/j>v[v{}ni-aK R޵;]o2 <8uQiBR>}q廾Zs-Vc2!i˚+^#hSþ2Mխ t:e u> ~6rڽ3~W 2kh%:0jie,䅞 Gs R5d.V$KwvTWg*p8|kOUJY%4+]62F 7ó#A Di*G\5s%i ѵvԹDom*y[rwW>'l6pޕ)Ez/{O4WSPdҐq(b&'gᴹ "˶٦J|F%y)ýA?6~S֎=z"7ߙH NY99;'ĀBVNlz;썇ҽ"~pJ$nSg䓺puplH`ddf3꫷Fw+&1%鮲uƬJ[̠1lFU|k}M;2lݬE'_Tw)hc"|Ƥ6 O@ Yh.{fRwYsS02OEڀʹFWӁ|ڐ7Aj\]DQHM`&CE/_txDm@.b80ˠih6}L ZgfЫe=vm+5-nWl i6]Ӡ+M {P ZI٭*t0wĠSH6 G$"Dʀ ߀5‹8>W,@ۏ2ȉW1JEug!,ڴAhI7\ZuAx V^GhB ղAodezEo=rjiWn^95n4F\]Cqv:' HTML (Erlang)yawstext.html.erlang break_char [\t\n\f /<>]xTO0ކ?ȋh4&4QtM6;|[ﻻ6:xKyA~Ǎ4ts+3-'&pm  4iK<)hj)O7*DD)8$/ BZ]bCAPԗȷC![+fb㛮>>`{Ѐy4 YYl#RBMq.%N$YK t n[7걗ԫlQr1j*A.`&7Vf;*}zAy-žii$N> ,ă1lu|ƗeA%L !/UzޡQ\eKT*]olLlPXMS%/QGit Attributes attributes gitattributes.gitattributestext.git.attributesxڭX[o6$mIa[XȺaNQ^eQHWQV^07=&v=ۙ ?LśiMUO#AN*SZmsu8rC{XKƃkU z$!ՒC}TKSZKUg9 IFpJG~Udd m}k<菿5RW꩘ƿRiHV}F{HQNG3vh|/8gMM~mj w u~R6ݒCH#uUޕ]vZ, HH\U I yr.ĕ1<`S9b%JU[8p"f$k<8 _l\B{㖮Jn0uD||>;^U"x3<~{io~^{byC1џDF3:mޭުe&V3yPvGF6lզEmsY ɗ2e`e{K".v EL)D#r;NM=s1ll*B K{>Y>@]1BqD!VTcJoS j>3PᥘEV$Q(/4ròEf=g!U:_"/ۤK;RڟMi!}wY:JևGYےl)Nuر% e1huvhDsnc K _Lϰ?W &]z R™ |wg}#?Q3 ^ѩ+Vcf}$%y6.#2WODԖc\o |K1TʘQTm6WF{0~\1xC% [mΚ#cxr'㡡mhOPͅ>>ѥ&AR 5M5Ν^eb,HզA- qޏ'L)`2Ai\.۬ HD%)B~2ákNmp!<PrÒ|r!lq5ypaW:?i7/|R|EVppu vٷ/ddˢY ͯjUu Git Commontext.git.common comment_char[#;]pretty_formats_builtins*oneline|short|medium|fuller|full|email|raw$pretty_formats_empty_value_modifiers[-+ ]?} xoՒ5jfu:'MЖvH'a^f]lPebPI"JI ڇ;;-zr4޻J6FYEMӧ[+`A +Cd0ù [ߔ1jo)]?@<ڕИL~ϷLV`Pm wJSqG S&Ph BD%*ča&I |~f(v(QJ' { t %zeem d@m%+!xz(xMxKS?.soW̖%49p8ιx_|G>֮O1Cv1OqB;fYG)?J&(V8 ĥ\j_,Uc$96]f[u#νh>hG\-NRs❓d0*4q 135"`͡9-VѬbGD ŎPUVz'RڊQR||6 'Ґ<7),O>q&vK`*$ī9;\@',uMŔ~y{T1 '#wrZ\2 n(Ŀnp,uaO,0YyɓqZQxTXT/dVXy@r1GQΠ%$݊^Tq9:6Y bDtIZN?L2Xɀ@#Q]! hLamz7^;E :Sts:BD=d)esi!moXNh},ҡRn +SvI6 B hqd$9+DOeEwnĶw$>W9X.?HZY2ImiN-l_[D5G2RɀR$$qnKWNmW<`a.Y'Rg,H}oV!߬3#JBݖ4B񒂒N*lei,t{:Y ,t} {(5'?*nOBh!edPUAQv=܆ә.Hn8c0Gy7M;39J{X4gh$' yI9/TV6$ċU a*bG` kLkO:Bl9KV=CRju LygSf}+\k؋U.rX2#\2H nr#yPO2Ahe d?]}Ġ 1UWyP+]) n5ʋhjk{}em3*m>/k+cy+?D^wN؎͖_Uѐz*$\-x,DpŮ,HdKN<#rvkӾQIZQ8ms[GFe:umD$^4]"b_>ތ3dok .C4]հ^m~A|\j,Ot0j0Қ0yrpYԥK0l1 vKӏ  )@Ў5B6AwFQ~s[)V§4ik3"Vvfg/#&ޮXfk B`CB@a}LI(4kS: )Y}0⽦x4oq27"*LSb4ǝML=Ya Ex=wvb-/Ķ&7ycA,'h+V^V-+`基J|Fϳ ]A0GB]eKt<|V}O @wnbA2ߏsהWX/JʝݨGW5/3{l܅WGeZ/49 {M'AoqiBk'!&ɰŪ!1s;VXIb`ڷF*)/ä5\aM^.{7.Dmb0h-}X_H { `q/YUӍ:~@Sv 7yiL @g˘b ?$BC~C*ѦD}=Oޭ Git Config gitconfig .gitconfig .gitmodulestext.git.config ^\[core\] variable_name[a-zA-Z][\w-]* zero_to_255*25[0-5]|2[0-4][0-9]|1\d\d|[1-9][0-9]|[0-9]xY_W6 PXז҄nK`qپx[rXٳehv';#q(HWWJԕg'qj=q}C}? 1Vq/G3$Df3نtmnhAϷ=:ӞrsRN_[M<-m]1g+# $_g\_M:`z!oF`:1!'~pji18K0dbvL6vG `Hy^ͩHƎ̩6 DBAmAwI\zIv=ߥ.Y™8{@ rC~nt7 al6J.Z\Z$s1 *%W?8 9VwG-EU0`~oUA.IaT3|"k0o`'!ቜ*eו hs0LNCDqȽ '-xbY[E Q?gڬPrPо(v;lY 2]NJLL Ow Q(cgy8=4>bȉL`BQ1&QoCf:wTK@OGv[aQP}2`*q(Jo2Ʌ/.qN,3b璬`+]bJX E%mbs{n0I7RdNdnOrLKX?/W0`%^֫AV;|T⤲mM୮&QKܾd|+ne5YKX?_EܯGe}wk-RC* BXP_iM~DOY tu(NCGPG˥QK+yÐE Zxýq_ѱtZS͒P^峎$7eD,iO *.KMR_ʬxVE좡3*41DZ`Akp£tTzTGW%`g QCBp? 8MM5%Z)ydvkOZRkA8]Q3 Git Ignoreexclude gitignore .gitignoretext.git.ignorexڅR 1 8<=< Gxu-ֈ ~- dҚVq͵Ş*W|Zal358KQĕb  d%:od35PJ`i2^r9<<\fS}Oc)=D#rspWQ+yrsdvZz/6vv>d/SUҏs0kPG"Git Link.git text.git.link^\s*gitdir\s*:<xڍU]o0mѭ&!>& 5Lx*~XmLj-qBgu[_n_{[# ]fw-i-a;BGT+*WMYT5 ˔m/О}{B65߹P=q1*W`hzdO(EHţпKCI@V&))hUq f]:Nfߒ)zz<0p*'qyi؏rotB)Se4'qqn wpuEd=?.디rje}C'=uMl 5<ަwpekį9X(pMBfp 3KPKAԬ)hႲ'$e|J%h{e6qiTKzol"hYe)p0YC3ֵR>q.L՝kYWqgG{}|2 "s(@CfZEΠFx mh"g[Llx]rU z~p)5c?27Git Loggitlog text.git.log^commit\s+\h{7,}xڭTmK0n;_O~i$ dl-ek+svM46C\ySڥ{LX<&bqv\sNpLW>Ex$j%qM P;O$jUN_e= N:FQ F2Ā Tbq$htBb "я.YC+G\G%0 g04u5,-s,Cο[@Ҳ;o˩āλA>[v+qJs&ƈAϔ6Z93: C(J (cq,6+2tnݪڼj|z~9;t]?^CևDA5. |S3ԋlC?qf Git Mailmap.mailmapmailmaptext.git.mailmapxڅQM @ՊtcP,GtTXMtFYE]{3~>r+N!VK[mRSjV#=D@iv/IJWFmOW -޻Ǎ}0[ W9d7 )cwA^A.2PZ;yZ;sN•N9ɓ6ai>rUAp=NGit Rebase Todogit-rebase-todotext.git.rebase>^(?:drop|edit|exec|fixup|pick|reword|squash|[defprsx]) \h{7,}  comment_char[#;]hash \b\h{7,}\bxXmo0&c-ݤM؇I-FJmSX?.+rIgeˋ\!C!=׳ZfCFcǶ1sޭYO<,{L>EL b\ EHB\o{j fȟgyg"Ad 699"ԢZQ X]wxW;_ig-!R^t6HK2 k1bB\IM) 8T kҬ6} Ș8ǰIjHY:5=ǍĿZOn.ĄO`=xC8N]0dLD-h>vQ1,׃kuLԋ8sa6-5G.8pΑ9q-x{Bp?GyOEp4Ab)&)*RwCDӴmi.'o="]%gsIu2<,]Iݐ3uω+p?mek]pwV"xrzJwYloE/FQDQ4$MI I:MX\&\*^&oQUX&GE 2ɒny2ٌ@=P%>ʋ_Ψ4/Nd斩Y99_Sxo`BVg\͆ɱC׳pz;t=mt k #(eFUKUJao>j~-C{p[$GwNnSnUou2L{]aAQt ^7+:5^]MSa:r*U] #@6Uiuʾl=D\7*YžiGogo source.gobdigits_?(?:[01]+(?:_[01]+)*) bin_digits(?:_?[01]+(?:_[01]+)*) char_escape)\\x\h{2}|\\u\h{4}|\\U\h{8}|\\[0-7]{3}|\\.ddigits(?:\d+(?:_\d+)*) dec_digits(?:\d+(?:_\d+)*) dec_exponent(?:[eE][-+]??{{dec_digits}})hdigits_?(?:\h+(?:_\h+)*) hex_digits(?:_?\h+(?:_\h+)*) hex_exponent(?:[pP][-+]??{{dec_digits}})ident,\b(?!{{keyword}})[[:alpha:]_][[:alnum:]_]*\binline_comment/[*](?:[^*]|[*](?!/))*[*]/keyword\b(?:break|case|chan|const|continue|default|defer|else|fallthrough|for|func|go|goto|if|import|interface|map|package|range|return|select|struct|switch|type|var)\bnoise(?:\s|{{inline_comment}})* oct_digits(?:_?[0-7]+(?:_[0-7]+)*)odigits_?(?:[0-7]+(?:_[0-7]+)*)ohdigits_?(?:\h*(?:_\h+)*)predeclared_func_\b(?:append|cap|close|complex|copy|delete|imag|len|make|new|panic|print|println|real|recover)\bpredeclared_type\b(?:bool|byte|complex64|complex128|error|float32|float64|int|int8|int16|int32|int64|rune|string|uint|uint8|uint16|uint32|uint64|uintptr)\bx[{۶su65q'HʥKS׎׮ݵkuJCɴĆ"J=aO{^ !)ʉ_M:88887wZl ݭÁ'cDm}"󠻮C-eMm[.Mm7hD.%*~:PAA2r:QX?T99$)E 8V 1:UlV76Il F(c)8Ř3R0R0)Va"``m 1 iK\Ph*``zPh J pW "c7*X*c76B)G__c]:cd# 3s.hYm1sJ†j؟]q=`,8򼶋XzrBA͊z2&iV`a,۲]4{;F[=̃;戥m. :imY1`r7X1Q1> Zirmvw\S>c_p }#/d`xf#1({.w@aS#JB"}q_sY&Y ܈ߗ`9،Ɋ>"lpb A݇ {!~M?P#r^&֬ezmf, '6ڌ'h8ә LDVǁ$C@7z-i)pG -z}L`ȶ+ܤXol øE ㆔Cz c6#iqӶΉ)8ʷF_U}Fs!8θzᐴS0g(HDT x?~D!g@H s9LJ'18ImP_@*lsK!BԖ8 ~_*frHb-TjQmna$<B+؝aPMI q"G(+ig?@P^ӈz*]ÙN'1Ah44 CT@tFˁD1n PGAO "oDD[2#7ZzxwO?ʐU@zc&T_/7㥚^nTx __uaì9Yh0W3R8;h3WAH_p׏DK"#ϗXC,b%}JkE=!Gc44ӓTekU6+PƠ\jP p3 2qZG1[!o=ʪ4|.c2ƏhݝۻA1ކw;w*MeUPH7 v_ϑ&,ƷXȨZ1\$%݄h` FN.Hx~Jlk'8ɃڡNrY˪YǑ>_ΠXqQJ\Run,މO`r,Eak8=ݍC1^NKtN&5hTD6!3SpTL'0!ALAvh .UY%z;R|VtpyhC^̊ 'Fg{t ^Rӛht-M[Sr(puhvulղjul˛)a{+x|Like϶Uk|mj 6 ;*qi*YM1v:ENr sKkQ鱿PyҪҊ!HOvvB96t<8x ;߃n݅/ x0ppA#a _}OMV AΪ(1=5f9c0Lx BgÅs$~d+ŸJoKc L9εufIGeIߴ'=ڛ"

(-ݬ>h¼[aF,+/V5Q[%a v@0E|& 3"|j cF,f= 6{SH(jHV,Œ{Q4nj~ 7Avqmf+mD$6 Bq6Ald?m=rE8q3AJϜ'CXfsF^sbkzcrĉ/\o:^ZK\>5#@6#qJWHܨ1ԸL,g\xYnPe'9Pk2Dj3Ɛa^Tؘ9 E4Tαp7A_pF lǠ LL4tGze~`7 raBYz 5mlzD0}O<ݨc]͘7X~t͑lS9#|qv  敥J\QXD+1}koٴc-POy(JI#>\cTVIL?8M%+i=Se!/! hj&fFC Ә COV?AB̚V -ΉW-w,= k'([P ojG8Ǽȏ|Rj}DS$Ș-LR*)lWn؀7x\9bH(ل I6 S D2FL>?Na $WSvC(u䱓t 1) 41 WnfV< qGBY9\y{^.`[j,zqRJWQ_4X^iOVV P蕕|ZiWz+z~Ʉ9z䮘Xzyn rOk6r$͗j0{`ױl6F,kY[򊠼!( ʿ/ n<(xMBABKK?_gM'hGd/\_oΚ:jY=6nzC\¾f+g{ªBje3tG`eXA=UКjyw%oK5%ZsArT+>*mkڵUFp>*$Q,҇oi8kcj-I SєpOe1[T9O2]<ǧisHעMZI^4^]y,aҭX"zj7wmL4š>>.u/ ZMO%"RJPЃ)7vGbs7҈eEYO)Nh tAM+_ЭKP' O[IaHHI!I&O|LHm=M޿Ke#(Nmm5x0S Y~"3gf2OnxI<)h{q3cr}QwL&/0ڕI|Graphviz (DOT)dotDOTgv source.dotxڽWn6tM%laKɬ\v5`WqgP#qHepoCJ)Z"/#|!}8~QF!G_ڿ,a^SWoԯfv˖C82ϖlDMc!{\2޺Ӯ{c+_3'IW7A[C8l% "8Z;.-fסּz,JI(5ĈILccv1^rʚÔH2n߱ALJHJNRb]^M04o 2B1|] Osii>?eMZ-TއCG71//%V/oLLޔzM%8wmo[jg T;XHޙYԍRhF<"B^6%ջj?v/˭Un5T W[Aǣ>a)// Y̮wNn}PmcGy1xQgClvr>v.};yMtsEN؆5 cQ*&˄"™8pݞdcit:Q:o&ƳVѾQd]&C O:<]LYgȾZIH{Sv_,P3qUknzO?]3l~Ǒ}cwڿGroovygroovygvygradle Jenkinsfile source.groovy&single_dollar_interpolation_identifierD(?:{{unicode_letter}}|[a-zA-Z_])(?:{{unicode_letter}}|[a-zA-Z0-9_])*unicode_letter(?:(?xi) # Valid unicode letters according to: # http://groovy-lang.org/syntax.html#_normal_identifiers # Literal Unicode Escaped Unicode [\x{00C0}-\x{00D6}] | \\u00C[0-9A-F] | \\u00D[0-6] | [\x{00D8}-\x{00F6}] | \\u00D[89A-F] | \\u00E[0-9A-F] | \\u00F[0-6] | [\x{00F8}-\x{00FF}] | \\u00F[89A-F] | [\x{0100}-\x{FFFE}] | \\u0[1-9A-F][0-9A-F]{2} | \\u(?!FFFF)[1-9A-F][0-9A-F]{3} )xsF6!?B'"?Jiq; E\I&IQ_q>ƽ]iWծ$'Wn$Wo{\=_-^o-d3z(HvGHgyrCCz._tFAܾ vQ:drz~p,wgbpP?Tao|+iCo,II˲}ض>YG4gH\<[sL7q BQ/>] lwAFs*#}nn*y>[Kjc^/&3N=O\>Iј'^&]Oa|VaLtƨ1eL 婇2L>TDT6t '>_ [x{oa hT3O\'>!lFn "Ām\ (HDҮKv|{ ,ԹSe\cdYc4'Dwm**ioEя*gOL/ iU~N.uA81sW@]cCxNF] abuJ6-Wt䚠y}{`s8givJy^ tF=Ҿ_C>@NC2$;cau!os8c]\x1=&R+!˙m 8:+ Bn5k:bHEr/dƘ F HPm $-xh`6F9A*Z{GZe9ӕ^Ե-;k7fSõޭuS Wg=/GvȷAI=!P=+Xd6o$&1´ϻ_ԻlbȀ2YK*S02rJ^ALpD? d ,W¾( L*גe }u OrE*KRS6P z7a `g*s9+8 (x^WnUvfǂK P,Ё⢨$}ru[c1+hOǤrTm8\ p%S #\n!lbҀU3/x8g$fuq8Gr2]^1ؔje$K1 Đͤ!!w1/?a<_i]sFEGT`4p<+݉bmܭB7ƛ`WucvU&+ۄ CCjjTa/p]0؋ύ "or񰜙= gԛ~eMnN¡p%q9f~FmFBKX̱KYRy8teT<) ͦλjUЯXw=}6@nIɬdi.U3ZryVV8-5K|}'&eɂQ~eK|4~o3<7(aRꗶd]1-MĈ/&v\J{*)MsY]K]3̙nFP/ٿB5,II1O>A+|O8g.C~Ԗz jR~HPGhJNzތM/q,%{!Kw.wZ J2:ܠwyy/+ꎭ20EҁJʻ0|cf's*W:Nrc-,D_6%}uA&|%kW:JYVԤC]ce}2 fiO'A ԭ+DcD [PcS#Ұ7.䨹P9J E1|Y5 f#]18g2g*1W9p3:Ҟ6Kdop C)a_>Z]EeAǵAƁ!5@4 :D˚XcEx:DSx; iBs! ^ĨQ9ɠUaa< e?%9T"+=6[yqP+B!מJQ0W'M=FᙉxE7y_5zc1w<)ľ`(+OER9d8 ۢY:>gg49-k(r'"̧j4S&V%5TxH"pCn(ڗ1mE}`ӊ15EEh h81+2ٷG~D6pL9FT)|F]#3BZ5#rW&mT+y,vpA3r 2k9*bFOfp,uDM|`wZ'@r'TP: D+? $GR:^!#f 4 Y;|/Y)W /UB=ˎ^ǹ@w'W)Y-^O*<`^4 '/+$\F<~I]t1k;cULo8iz;Oio&3xJWgw1wKvy8=UPbQ&fPFŇ64!75#߉6W5e\#âOQ2aKV8J|0V`NгG zD; d3U}!uh5m/Z];8iATC| qkI礱k[/gNh`@̏w'#2+@P~7aojvMߟy#z2SdT㢍ɀQwmb˜N]2I$tHr,&RX\) *JE+^fZbNNG?.61ҷ6h.fH}kO/2:y;zSt]OUtXj;ZWoadpSEc[vi,u~HbxVѹ͟VvrGЏ+c!9}x\1qS !ZXhPg,UOV'+q"eӠ@7D⿉W?LCB2"aMe;~A^/N#GU|W_߃bA 8 < ^9 OQ]attribute_name_start(?=[^{{attribute_name_char}}])block_tag_name(?ix: address|applet|article|aside|blockquote|center|dd|dir|div|dl|dt|figcaption|figure|footer|frame|frameset|h1|h2|h3|h4|h5|h6|header|iframe|menu|nav|noframes|object|ol|p|pre|section|ul ){{tag_name_break}}custom_element_char(?x: # https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements-core-concepts [-_a-z0-9\x{00B7}] | \\\. | [\x{00C0}-\x{00D6}] | [\x{00D8}-\x{00F6}] | [\x{00F8}-\x{02FF}] | [\x{0300}-\x{037D}] | [\x{037F}-\x{1FFF}] | [\x{200C}-\x{200D}] | [\x{203F}-\x{2040}] | [\x{2070}-\x{218F}] | [\x{2C00}-\x{2FEF}] | [\x{3001}-\x{D7FF}] | [\x{F900}-\x{FDCF}] | [\x{FDF0}-\x{FFFD}] | [\x{10000}-\x{EFFFF}] ) form_tag_name}(?ix: button|datalist|input|label|legend|meter|optgroup|option|output|progress|select|template|textarea ){{tag_name_break}}inline_tag_name^(?ix: abbr|acronym|area|audio|b|base|basefont|bdi|bdo|big|br|canvas|caption|cite|code|del|details|dfn|dialog|em|font|head|html|i|img|ins|isindex|kbd|li|link|map|mark|menu|menuitem|meta|noscript|param|picture|q|rp|rt|rtc|ruby|s|samp|script|small|source|span|strike|strong|style|sub|summary|sup|time|title|track|tt|u|var|video|wbr ){{tag_name_break}}javascript_mime_type(?ix: # https://mimesniff.spec.whatwg.org/#javascript-mime-type (?:application|text)/(?:x-)?(?:java|ecma)script | text/javascript1\.[0-5] | text/jscript | text/livescript )script_close_lookahead(?i:(?=(?:-->\s*)?]unquoted_attribute_break(?=[{{ascii_space}}]|/?>)unquoted_attribute_start(?=[^{{ascii_space}}=>])/xzFrlH6! $g c<sAB9cFjkΏٷo``f[jIӭ~;L˭RUk^Vq;;0@1u@n VipܾρwuIaaA5 yK s``Q\g0af!5 ^ܞ`|ózfخ@o||"[GekC'e/u=LDH B'27 /HܞRփZaR%65gx$sښ-intOp|p ~JUtqv{eOu棞遛!}Ȇ8Dx$bBg%я(:_5%RJ"&DL/FM8!EL52X 2X k%:?/DLԦ.l/fN [ p(SB$AC$vsZC#v{FDaC"&vU?0-;5ag]}]dY!A6D=;Xgo Y doe[e喰 BK&L$@M/` A~ٸ|!Ѐv, Lǽ+,z`\`vQE#0NKq.'ix@#g_~XAxzneH O%l aܞRjP9Y>nOSn#a)BnWkA ? ^nNj%moUw ㉠ Cd!,#D@E"Fv!~,4aO! 2=,p,'CFvWt.s׋Q {pIɁg %q ԅqc(34*@b*Y.1<Ԗ(SG!Xq2n uvWu1 DW=tKdG++ E1P,שhr,zIB2TC¾vk\lZQմi `--gu˶Q[-}{wܡ#M3X`D3jcTk,԰.lh !ҵny  "Zm[> ꛠf4W$"Ad> f硞]OJA26Y 1ؿ(t/RV\u~V*k S -G1B[uD޷[g%Aeǃ$}U#@um!DfBŐ3yE~NvqcX"ߏ2}>' ne3Ylܮ6@!5'EmmD6;,:`!A pJ)paB oXA'  w]_h1}R))*F5Aw+(?xl1vq1߳3G^7 %ي7q*_?eN处S<r**>'ϡe}9Χ(|r_aL}aMu(g^š|Y}JxMP(돆LI4U*X*^Ұ@4"#q%&o/7\Yc`ֱ<6hbEHeA]Ym$4,!1Or4so"S6%.K`Bã /M WPzWq]C-?[~ޖ/岱/Q1{"Hj[9IFGF.ى&󀇤1?\UiqV0ώ@3 ~k#(:%Zt@]WVׂʂԖht폕q\09rIR ?ACx>. ǭ"O䃋lcǗ7]5;j+I7:`be=.>uЦNvUX$. CskxmyJ@@$ՖA@Ddd`7(- .ѽzJ|a".+red[:ӚԹKU w'S!FulmTO ?_ ?FÂ/Id6[bFxwL_ث:{4X9pJltF.# s]|Z]=d7:zѮ^{r<}3]#Pgs'OAMc$tnWs #"\#h2oY䵕=ضQW<۟դYm5%}9puFn]0O9wm9~6*vb$a ",l/5I;T!OK04+LÑзhPSeن;'giKwbt𵢿lTX( Qv=D9^ٟt*o@hu:XAlV Wn\7\_ l{'$yAJ@"H-P?T*V?+3K5[݂OQSK˓kx;QE!5*r>({sR}y>(cY,w{aepbgRqɐ$S~P)93m$+E#0a>'0at0#0|A`fa~O`ޏYX KK`@]'Pph4總S]<92mrMk>y~<]OV'{`u"PLs M=xr1<H){2qE-~pI+Ɇrށo:v]6%aFTFpe)iſMp4I2m7CDҔIڒمaut䎥CJ!.!1\;oͩyGdfStzflSWe9LDL#y8vr'^DG? W-tLC8} ^O6᮫edP} kml9:!@W.#Tm3 Z!\e. ~0Ct7Rythؿa5W6l~UB'X,j7.l¯w&,4lFE|>`6RExa`col7^6vh=nɼ7vP9B񑸁i>Qh堦՗EeSWw5Ic8_5jT5:~ū&5z⻎%6h gv3^bίȒVR"Яx$ >ae9q>l<@'=p˻%M='rKNm'Z;S? ULG%pU:,,-,Txy+RaMQwJMe=<.ˏi=DZ8s 26+G7b-uFbpދ F |DgP7h-"( tAyd`Ω=7IAgu:wx8ԐJWRamg\6[%X*{*s^-WPک⮺_W ܫ4YEFm^BXVEJq׶պZEJ""ХZTas}̩J[5^6T\;PjLM0RLdi-W\ BT0hCR _e.|e[qIMjdssm2t4),QW\Z}8{ #A%} #FLOf)υgiVjd4FF}cDdI~ivt؁_ ]2H,I.4Se^$G&a0#{:]h6i3$}`T`i8Mm,BǨK2hv!"hC`yOط׳fP*iy9\5Baiиq4Ó|Opj4UЕwA[{ =i:6g "0Y d9Lޢ<%kG8<gbӴ9$ J䘣 qMb9:2|,a~?#(&_VNV`8?"g(@e M%C.b^KgH3yFZF|SWpe|ߚu-ί#ҝ^mp^B3L Í#Fr̳ ublE(\KS3c5/ 2.#.YR0w ^#k/KD-+yAqc1̣L<aY& gҷWE <Ix52DDHF EYw0sz)1S:3xA:-iȽ+^e-WNѸׯ0K.}[2 T'd?k] =| OƴY.%I[y@4L~ ?uʷ;DUUEF+zd9־K#"} Zb!ZM܏ %iKHڨZO3;|omqmJ_9N}mk T䁙U3z6*Km*YQj[;Թj߶"YY8 S=V!z?*TFX޻c{m ۶u=z1OґjzZhY9jq:ou h[p/;q@+^Ateۮh181Zos0Z$ؙ3#@;( cbIx ߩ${6OȜV\~? Eſ6_Dh<,Gx$ib!{_kS&.qÞ/FGuvR8Hkc,}?#lZpvxӖ04"!*Pi)Ia!O:ȷN:?:Ƹ?H ;(3$.iLw)i*o QYJΒ xIyK{7 LHCEL+,nyor[}haO"9 WjT;5l}Z3fE|f3UyJava Server Page (JSP)jsp text.html.jspxV[o0n]a<!AU(~)3Unbڰ -{w98vڕIH|;NΏd0EJ#aTM9|;=:bz(lFt`ݲL69cJlH0_fsdNYty44 d37pO|R۔1SexXFA?:Y (I8g0Z%уC93)Q#ՠjbD$dd( *4̭_蚣~)KNMůr>zU2W_=Sd xcĐDž p䡰P<"^1NB1OšǂO0w |2,tM 7fQ;t2 M)y(kn<b$}msoMb:{e[;bK8_zn[(WH}GQҘytUu%WR`uPN;żW%TO)2'JtM}_%9}kJTm3ySEUvAfe; lowercase_id!(?:[_$]*\p{Ll}[\p{Ll}\p{N}_$]*\b) pexponent(?:[pP]{{exponent}}) primitives1(?:boolean|byte|char|short|int|float|long|double)storage_modifiersj(?:public|private|protected|static|final|native|synchronized|strictfp|abstract|transient|default|volatile) uppercase_id!(?:[_$]*\p{Lu}[\p{Lu}\p{N}_$]*\b)&x= ck'"$ vG#%(m-JP˸+im/w]Ɏz_ܴ޴?}?ݝ졵"y7ケkğpzt\gΜY^mlzjך;}?'̞:5՚=دDNy8=*5=sBЦΐBq:*RYg9-&7t ؖTPV:ZcL]s6 F ?&۳Sv6grYZ.3Н8Cj]YN-),rVVs7NKa|߱|O&pϭ8=&-x ljs_ Z!*=}G Nv]E:zpٗ$Ent m!Q_t۬^ /R@BRႅX#wK,ZݳڼRC.[+F:uzaܓ H6eG u̥f ӇGFW1ۂ*83| 5=!UKn߱7V37ۈl+Pc`P:(` f8!ųGȥNJ!V{^%ME(nE(Jv@l]6,hXތh?DSFt&_~W"No F%I fbaѐB?Ss%XOb5Aot]AtD\E X BT[69LBohBjjfHx 9b="1GJ,t(:\66 0 `D#1 %eGi:[vpWt/ xdXjKhؖ h(e @-َs)TX G|0B.dzd >Цo--( 4jVpE枠O$0X تlz]1Ly+v2=*9k8%KFUe+,8̴ 9껀j0AƓ-N @97Sb@61 XO~o fy.@@~4эׁT%@c$E '@]7qzH X-@I $xwp:"`.r,> /!l.诀`H5NwP@h}KL_ľ)syw`.E@!ՇA;ȾA ;T 4=1=G[];73"qX_%WdݪN纻}E` ߅xˍT#!T涳:ތV_; l] 5'$I~~9ZgOzuC; ߲=Zl& 9h4F.Q'%(mDL I!5۳*v@hqLuF0ףۄ7TfjHSùiVnNzmpqH6 ) CbN;n+,.xD8 QQ^t:VO ^V0ҭLu).x[ eVp YYkN~_U䫂()٢M+}zjV;fp뮸 =3~~v[ ص^Z9DCnf!##S99ЮI6#JCOAKe;,9PDs.Aތ-^L1FZNRFR8% gXRz[* 섄6\۶EĶ7\@9x-~S~e˔= ʩ<#b4 G\%2`%7J^T&D1|e8?T:L~S7$4q4iB~ '2#3%n}MUQ;[9 @dfmƑra2ᙐ >X%zh s;ez=AlOLEζn#;|4 -hM "UcFԩ2۸-x!`V;DO qmmy7q,D12rt338_j72FT6Д8X#ЋL3݋3iҞi)i4G lw!&΀̎*C^yE몉:{ɌJ|JjdܸNʇe6(Z"x o*ږe(@(PY[ U--bX]5hJ ?TcC?,Ԣ4 юic6kǀm َ*M1A/zzT:*3 F΄f  Jhq]U CJĄԂB#U]xBfaQRz)>#ny ,7S̭yIǔq3#c 3LI$<0",=ɑ6&D59]{pnv""kP }meoRPE"SqOA0 4?FsKrt\mBlu^+Ur2h' 8})\+ʣY6s-d_޸U*_5' }.Ϝ+=G^d^="{G ׫ASyo?d}P6i(^Ԋ,ٝ8*ߦ-WiyuIAx5"C=3(cN[9~g8o<5|H=zM/󉹋O+dp~2B?To/.# YYi4jZИhL$nNf;-/~%oݬZTiN!_DS$ccAWMꎻ`𪐬WKXаؾpL$i*L7MҥLgRZˢ e|hpXW[V7 +^.2,btVYkc"pa枷n.zު; ա(IVmF 4=˼ڹGN i-N6/ֳz}HXIȅbeh bF GN(x`u|K=aj Ki؂Pc\Fcp>*)yJ٨tniʢݱR>rCdgaΎQ#RlS7MA+XQغ__cRM?؅|RoDkg}kSˆ?H" ,w kw_+){Yu|b}n?,E/1 %tDF3T(ߌ. ӻt TR0f(3ERUޢ* 9k-{@F$J1N某qF5r9y. cDVj"pǜLN S+fL54^ГQW2/okJê{JvRkYE弰R)|h| UH#Iz? ZNJ7/H͇%_ h <pO+ 3oҸO7;FK!˸$^#ByJSƹTx#Bn'W !ܜ^1mt|ǧ=>敩96^%}RqE bTI^LV <T.݃\7L ʕz3xШ7LiQ4g5r{ȁv~t;72O5VKlSLyW"uD!mD #;3X+۱BF쥇B 11bDo]k&H$CC!@ ((sR(@㽼rt gg||$Z)<َ셜IcFY%ce /ȣS`wIJavadoctext.html.javadocid(?:[\p{L}_$][\p{L}\p{N}_$]*)javadoc_block_tag_terminator(?=^\s*\*?\s*@)NxYo6|#]6iڦyX%VO^ Zb-$P"2uIK20!0m#B0H:WaJ}^ \рilJb\6%e!Mx>& ATD 3D ΐ4_ڧ w2@ h?"ʲhJ]e EXc] "g"ʼ0rj2y,i RiFQCsʀz) )mL2lBC]Go!n[>v}7jtN71OjIXk|Vܤ't)aX>"H*GVS/jt,SĮt|]́ʐ n ,]-609L/uJϽ\0ڟ +(R;0B-78ZF Hg4Lි.iŇV\QIs>%WFM̭B8P^5xNK5jzu7Ca)oP]6]ֲqy[p.6mU6ۚCu'Ln/qb"-|X fJhe1laja+K؀`naAOIpa2YR0k2-,,F8e:8z`V26 rLďl# i@&5\vQ~ (AQm3&.Jbω#ZS哤א$>%UKvp,gOHJ=Zfƥi$9Hhbe~I+IqW|w\ #4QW¶TcIH =vŵpd(pėW'QBvInI3"# uRZ -EDeg ̯u6uo[eG6.}ئ16v}l;Fc*ۈZ(ƴɣ~j.3ySOj.VJI`%{Z#갾V~럒 \fz ~)`.vs>u'_WZf={׬{ov˹}-,b,1m+>smwd* UJava Properties propertiessource.java-propsxڍT]O0D#ƨj|ҰFd'q pߝлul9eƙ6f/1e'*B*V$/G/yQu3$Jocl  X>ؔ9_`>s4VQ!rssm}爣I4nAVq[JmU#B$rS6DLؖ7?HX(,h ) bin_digit[01_]binding_pattern_lookahead(?:{{identifier}}|\[|\{) block_comment$(?:/\*{{block_comment_contents}}\*/)block_comment_contents(?:(?:[^*]|\*(?!/))*)class_element_name5(?x: \*? {{property_name}} | \#{{identifier}} )constant_identifier7(?:[[:upper:]]{{identifier_part}}*{{identifier_break}}) dec_digit[0-9_] dec_exponent'(?:[Ee](?:[-+]|(?![-+])){{dec_digit}}*) dec_integer(?:0|[1-9]{{dec_digit}}*)dollar_identifier0(?:(\$){{identifier_part}}*{{identifier_break}})dollar_only_identifier(?:\${{identifier_break}}) dot_accessorG(?x: # Match . and .?, but not .?( or .?[ \. (?! \? [\[(] ) \?? )either_func_lookahead/(?:{{func_lookahead}}|{{arrow_func_lookahead}})func_lookaheadW(?x: \s* (?:async{{identifier_break}}{{nothing}})? function{{identifier_break}} ) hex_digit[\h_] identifier@(?:{{identifier_start}}{{identifier_part}}*{{identifier_break}})identifier_break(?!{{identifier_part}})identifier_escape(?:\\u(?:\h{4}|\{\h+\}))identifier_partQ(?:[_$\p{L}\p{Nl}\p{Mn}\p{Mc}\p{Nd}\p{Pc}\x{200C}\x{200D}]|{{identifier_escape}})identifier_start)(?:[_$\p{L}\p{Nl}]|{{identifier_escape}})left_expression_end_lookahead(?!\s*[.\[\(])line_continuation_lookahead{(?x:(?= \s* (?! \+\+ | -- ) (?= != | [-+*/%><=&|^\[(;,.:?] | (?:in|instanceof){{identifier_break}} ) ))line_ending_ahead2(?={{nothing}}(?:/\*{{block_comment_contents}})?$)method_lookahead`(?x:(?= (?: get|set|async ){{identifier_break}}(?!\s*:) | \* | {{property_name}} \s* \( ))non_reserved_identifier'(?:(?!{{reserved_word}}){{identifier}})nothing(?x:(?:\s+|{{block_comment}})*) oct_digit[0-7_] property_nameP(?x: {{identifier}} | '(?:[^\\']|\\.)*' | "(?:[^\\"]|\\.)*" | \[ .* \] ) reserved_word(?x: break|case|catch|class|const|continue|debugger|default|delete|do|else| export|extends|finally|for|function|if|import|in|instanceof|new|return| super|switch|this|throw|try|typeof|var|void|while|with|yield| enum| null|true|false ){{identifier_break}}O/x} Gu$_}dK;+Xj%j;ӻROGk $@.rBr@w?@$!yU]U]USj5SիW^zꪁ\%-:U Lϰ'vX? 8 K @ 2%pvS $^kwYm{nc3H]*iWtUsm6j6|:iN[N\ř$TWE zIhlL߷\j:͸$L(GIsHz z MJ8DLDksIzPn2D YW) Pi+<ػ.$b$l5tðMw$Šmz݆?Z& 88u3lc1ʅ˰T'- 3]zӌA^\e[N@|U@ݠc$=W R8ٱrIѷLӸJfD_<DŽD I$|%$=k h0ƌDzrȸIK ANIC.ZMK]aK TSm? |t%lzF7`6Id!ϑh)WĐ vejQkR,2bˬ]zHA|iR( HO)7iNwns& +fV" 3R9I(mLRGHzABҎ+z${` Q#W<ۥ)w"CJ#R7[Q@:ޤg4XF7I}4 ('- jQSnk[d'<`$&VדJ%U,)2-xvwCݼG䷿AvqAa(I&I-9{5HqP 7?"qhp=/pɏJk`ݑ$I ̧oQ'$i3%I͟FzN O)M?aL cM;|GEsqWB VӒJrLJӠFrs$)C99?z3C6hF~!8x;If(׽,oЗ /J~W'U_!XfL_ДmDZ:5YVdcmP %2,~$dH${zgO&ʴIk-akgwf/3Y%u3&ŕmmޘwt<]u{t{%5^ӂIKN}4_A|^24()J<.Ny.?H˻ j؇zAx&}5?@ 8Fjhc !haI6' rXLr)~~D&L4|z#,hDzm8cT46t1f=I(̟J q:kP $I/}Jt: N iR.ޒipJ TϜh?Aa sRM>^!LɤE.5s$V N~Woc䟗*0 Й_ )5 )-Oҫ &)]=]?6Qb܈FADsb cߒD闥DOʶmh4rȳb4x; W%.fU_—R 5tMfoH^#{i((#nJ/2ٙ7I䂁 6(F$u&-$ir38\L%Zܮƿdj8k2ؠe-Wˊ˿g5?2ā!$NVf5ῤIK] .]mI#.xAI (JSE|3VKj x04`=-'Tg-5'r0]SQI }ƀ(czA9 G:m[2r?k1[stg`$`Aǜj%|eЍ)p@;œnuр8rHK :xqA|ɀ8к9^Uї ZBCA WH,.{0omJArΚo>1Y;bc@ViG}VI> TCƁf`޲ⲁ*8eFO>6 MJqI}MŽ.V86I;=F =z>x&H t:^CM;*%ItS׫Fl΁s@)m9&м$TG8.YLޏ) ׏VJ*y|mIQ!|W_3| =m ~cuF9M}{tΝHzc!A3XX>X}IGEc \{6ǚ@UA{W`JFڹW&䄯DiFr;[HIK0|yl \b.DjZU1 ,Qtl S R`X# 塎7 aU>\FSk,6 oqR )$_P{]A9a i\^^jsryp9Uq[o'ϠaQ(U6V)KThiT w\y|F&}?ɳhߏ(6Z *%V ] rӊѰ:Hnб@h}/l^r4YC|ZGZ|SEDt ebN'*Yby .u{F'qQA+yD :IRF@"Պa}uDE}WR;ot&iZs%^D0<@ GiUC Ӑ[^۵\IT)ז4Ԏ˅%$^P??xX稙I4ř)6y-VȂָZX /i_Ârt ^􌼋Oߜ?tV:fEe׼hxoEg2 [0\$PÖkX(#hE/{#`C*&yO˕T"q:|U ]ʍM[?.s3]3>.ME>hTYm8ͪlr-,)$u iP?^S? Jԛ$V)QkE_V)QwI]D]$Qm"䀹/Z$*ۊ$R^$QJ:\$QkuHwDս󛫴#*#q"OI^D}AD*% $Hުwz{ɀE&̥y2/`LJ)y1n5Rw)*tb B7[{n9c!nzn5LO&'Jg:$D)G;uLWQEH/xKd%wrz_WݯE1E!dxLVžQ>sHzRg22ʩ|L<:H@9ǩ(ࢮ8nGLlt䒧!JJɒD<y5٢{Ri$qN}qQn _X.]0ojƆw WYdwxlqVA!,Y3(^Eߓn0qk.py0ّ@qG{A#!$2:(5ٙ* f-߬3FG50\3rA{kYFv~\:bhN8exό({sAQUwP% t+0dD̰`\N@~S.aJƌudN% Z: u0;h6S  D؁նaV ۩nFG%]w&X=H̀n5+ 2WGIfyv^gRWY},j IЮ?/,f ЄV_N3P䶎M q{Uf=w EP#) Њ0D,z40Zb"u( zZyW,# &a8Q=+]MosUYFuz_) :\L[:aԬ=Axc0G*-}8h 7m7SMBuBg*y'KP!JTO%ڙJ{)+=ig-*kVH깻?QJk7~NNŒJ7AteǤc_4Vn^b@,3=mDm^U$w3݌scP>1*Z߲SI?>ӳ(!HE[# x+(Mg! dl %MО>7< [Buh!D,>\8+smȊ'30C'wq܍ԞbT*!TG¼*M{; <˃フV\AQixFDeS69mםَ*\]Ϙ>Ч{r]Sy)Db&",^2 inصZ>uzEޫR ڦ{"ۮۆͶ*X2Ec@=UڤNgT͟Lh㊼Lw0=O1nrj^>"Rݾ5# I 2'7eb"h0S6ucwllѲ/׫5Z.Fi4ڦFA)b1A! 2ZER`@% RtsQſY||&Ɵt͖e:̒OyQ8)\5(]gp70#̣Gy-a;w:nԇQE3\+XD^cX}0>{Hb."~K԰bͿ.evMό)fUǞ>)@ڭg؝ORB7醛ּ'pgՈ./GHTSYRf>g.ITx9ʂBԟr\Ȼx!Z⅊T=!auclj*d_ѹ-ɈS8s\% _љ0azm)GG<+] Ui\ZGN۶F;mG5j( k1"bC5"bRҩ zFXb83v (#ۜ:wq)n;[Qe?L9|yyE^%E/Mq-꥾h,ks껫[X ,].ʨE~\&V1SQTP.۞h .g.& ѨGQU㙫p)c@(\@;:kv[6<[( )i[%`oj% =TA{#VzffvkyZRoW"D7x71+G˿&>=7dTsߓ&`s5̚I5:IDg-|yxQեRU2lYlۜFH yBA͉*4/ҫ̖B/ENVn9W>Ɔu&ͦԜ}Dݔ[~y>&}OWT>g uWKVc5. S="R GxyS굻ixd_g;ӘSZD?!PjUGWUhX/శ8_[WOxK ր/:$*$6҇J=nE5+M?] y;~!DR"^yUo6o`(ҫ5vCI;8bvf rT:\~R0.{]-oriV䞟)`=;TAYzR&9(clGIu;"&V*ԙ[A ߵ=]+~GaU@a]y[-IhrWnbIWm֥r 5`i 4a ,"Xc/g>ߖ֮s)hWGXiOt:쎣q"*3nqt$"ѮE4 Mi yI0%P$yA.WV-A;^2i ҥfm{PvEX⤤BP7ؔ]S0SLeŅ Bʈ,:t WrLe_Qq"+8ʣ(+oR|]#ᣚg ?1Ɨ+l3ğ5C&Jt?{eɑb*" ?].A[#;i-ZVj`=x = _# Vuh|YLncWQ*9g*^ycyoDߒN4S p!pS}{}{ϕ|}^&o6i84\IvFA~ɑ睠3a8Eи,H:t{uX]V^3oucDMא47>:IK"̉~'o\[ 4ݕNtnębkaI7o#y'/'u@cqcsO Y)db?1M/P5®(^r"fYVok&25hi%*lw1H QjEw }Ii`}"џ&YI$j}jio-V% I:8:iLCIM&9:\ /n#)Z`Izś)#<3C@,~@7Yr$UJVt\}>v7VמGEV395% թhs3m'i$X;@:JӒ3Y1ڄ9<R[~D +R('oZmDс&.?BqO c"M>(ֽf"ab"G uOvrZۚ2Jh "d3۵Lv,;(2go;w4C nmxLdδq4C5蠇pְ:is;G~ e&+nΆлl2n'TDkj~y5J|Uܨ];kwxvdhxdx;kb7Nw/zkrlECF_30,|Aq5wI/e o2`CZBX6аBjf ѿ]m}[\6 T9'*B  Nc89rtǃwdN?tlpw4<2GFУ{G 9K7A&O4W_C}Ԙ2ibb--cyxx 3l \&Y!X/ḟ`PL돘hFIִpa5Ym!~t58_X>?Z@y#:ȄeI3K`FCC|ex/^{%l6z&xwSfh(c8lrJtг_Mm=2[S -^!^j7 ~MTΫ/9xAо~Ke#vX'Mn_Z` 5b].}n!g^]Ȧ%<3.{1{pʉn :SSŻsK*sgඬAw ӽi0-YjwV F%=|h)sv%A\XW<}Ӛ\dxZHR=V,TS#`=t[Ϝ?2hdG4XH! 0I}4rsH!G$rs%z煻n 2q&6ZmIen\8 zSJ A0Ox3Ga6mIңfP9L/ ^7gH u`R>:~ Az{Ÿ~jvws&kjZrBC{D"SrgQt b~~maEY])9hi7=ҽ#`b >1.,[ذd_K-͂BnH EO;*OABi(~AG mS]&4'+yU2fӜ2:vJ "$ ~EI3_xF<;ktW}+?_EJmPE>#Ŏ80f-᷀6GR&G/ӝ<{;0M 3w٬z #k]wɈ6IH;#)E=).ʹ~#(&yUZFҤsi?% ׉lQM<#r 9򐞾ɄIæ6"T]="(*Ck*Ẑ{̝0:#jM͠a0cWFbE[1<q%l3eMi6r7),z>摪}B#U"] MWrֽQapش\<m-kRLEՖt'+&p4noW 7x Ls@I+9W)yBE[ڡ[X9^0Iaà_ Regular Expressions (Javascript)source.regexp.js identifier[_$[:alpha:]][_$[:alnum:]]*GxXoDokv'FֽbjB>784:5vIlsL?ɽ9!)n̸h=|/w_>^EuIw$tnC;nSmp0j .w{Y\DR`p~R.R$t*\ (]2qf}_B{,=q =? |ar?ApH&'t,^QOQ԰nP˰^ BWG~u&bI1gĵ_8i>N F%paKi!ej0iZKaȥ!óPZ8:@xXNUUj2`9P_d1idF03HZ |PĻ@VEN T(H"Z^S*RU%(HJ+U{X[̃^&eϒ2>O̟L0ErbMqfɱFMBq 󈄷OA:mdR5f `ey\%|:f=eה~ה{;bR|Cꜳ:qX+6Srg>?d[ǹ:)=ɸ/X24ƛk yWPʜmO01N=c, |d"k:J5Shk(O#ΐ!GwoT(HE^"+=YF|@CXl}6=A Xh"Gz{*LJ ' BvxW&2d*53W`>0?_ %k},sQqb6\|8Z\`܎4ۛ0. .My]\ԋanlYѬh5{ff}j.?&jV6 >ue):?_X Š#]kh2#O4-:~2fs5y{\:xL5Zafd]I=4y oBibTeXbib text.bibtexGxX[o0n]&Xm]BVOekSgPw'u8 &_6_|϶QM@ԣ!"߬W` =5`z4cf^y=`~78`T%K::e)z :B+ .?x ?|}x+`Rh]-o(9H7r-PGz IrN-Ӧɓl9xbvmΎžX˖}@ @%QH"F?13 >F(>|/ue&2AA {..p3o3f.uWoˍɃ D!}. W!E䥕I5K0qc,n+WjE "`wIщ{C'%Ebsi_9 ].A:(M6}mлwM{NӴmCg]P^/2@ %g'h_DiTu-f1WҦ$2C<*j>U3]l*)4++e@.ֈBu |ǑB/GvCKT_1Q14σO(v%)@ P@[WڙRpL3P?(L%;Ds{2ꃶocYph,r J1ۭ IwxIzTEib@FksQE O4z33\Ksf8<1B)v}vkhqD#3BBZBL7D("3+"\a!I_9gxj `BP>ؑjM|qKaG1)5[1,io0kZ?Y-א}aO)[oCNS[Za\ a%HX\5?7;G50S|#üӊ4iwqPuTpB-k^K2cLaTeXtexltxtext.tex.latexnx]NMl{PNbMܦ6iZMQIA {{t${ ;pwR$MZc:|qx}߽p\6b3Z6oc?J=}AA$eiЇ4+ zy}zE} zeU}n|]͠fz +jrS-VZp~Ī}v+NJ5#KtH`#yqWPG 7J5$[_/apcz]t:6jPOn˞QtPYϨY ^ݨm'8rL36j`MZ(YHm(@ҭ nc&ՌCXbC,ʫPNٰZ^{ Wc f-G(@^̿O)@ q~ Dt5MY3gtr\P rHVT(@} `*XZ X< ` r2=@V& `ZX9+T `ArV@3:\+Q*@  XyLѣ9}(kZ0f5bO$cuH MZ(8-Z(Z(8P`Z(} Oi@}I  fUiR)ަOBAC ^Z(ZBת Z(:Z(:le2M۵P Z(ZBWG ^]-xkq-xP t&sC ^OhI-xBA-xC ^OikS  o NT|i Ȓ68gq T;%IwCxrf^sPyu >_|_ǂU_ǂe_B{ =|z,xXP^JmI;Uz,Pj=dx V_ůc7@XMz,Pf-`hMDoc@Xz,PN=(~ [ߣO ޯbBAjևPpkɖYqU?Ɓ}\Xz3\ d R~J9?C#}FoVgAjV\\.lTp0R_L|)חS}%CU'LXBR0ZZ|%ķ!w_W]?0JfD! 7[P<3A}4_&wyk2A~'d _ {8 Knɩdҕ~|%S3R#;y8|,E-8DtW|['xwW2LЏ8UɄ.c>%™j%rTsϸb@u9f/_snpQ3Y?WXɽ_…Mr7&]Q-r>QQ7*njDN%NHEJVmd"rg^ .s7X௜ImE^Aj)r+kTG|%: M`\Xf6(u_*g[mr? $ˉ?#^t"a`MT˱¯ۨOec܉P(X >=ΰnx(袢d7jNB3JC W6g\ό߇ٴ/=_&ҒN$UU6f[Ri<8 } Y7 \iV܆D∴^FL(UKe5ճ*QS=zZT7:B~ܢ)mJ-Hmt6wVFVqd.f+U~)j!ZDؽ{5ccR0V:U sB_O2 H?o$VbcI׫N`sx=1c'(^ʨQۆ?={3<˅4XNe)n%jda/yܭ/mD1^Dثmxzc1xiwi7AFPs(&`;|Q>m45үT5DA13hSCC4]8:8]wՔZ-QJKWBH^B4ξAXkNX&=ݓ·!e4#Whmµ9a0tJ1چi -R BXan@bRUZ˅BOM+~Y?E#;wo1wn.NcKyye6324:|<OZo_ m\N׈f:kg]Hh:y5AX3ؼci4@t&/$d*T5}ʚ$cdp{B^ NOUӯxV=+: ͌a'UPg)L<_ڱr[7%gCЫ$`g"ZWaH@P8V XB3ZBBhrYB򮨰B$lՄQ4lZh3dJiiF~J#k>."4hҫL(*m5W1| -nsֵ g6#}HH4F!HGDeB&XCT{#w#k 4փEtKo<0Kf!iȩʶ+F݇K?+r;X͆f﫩4r E|OZ粶ܰ}*׈辆9i2҇Pi?HIgJxCBTMDi5YH=,|B-\>c~HFх &g:qY[W_6:4?J#r3aT͚Et?\Z/C#4CE|?20뺗uDNڬj* Y,2+y<3JsE*vፌEp~b~?3~'1FF" أ% :2!5#a8J^#,IፌEKO@#rodx-^x##`MEmVFBK^7!,፳^Epp_.gB=њ aoe$,8'2+Ugx##`"bNFPTq[Xž:$Λ&sQqa9!ፌEMui(93"Okv:'frwlM̏jſV|=sG2'/ '/=}YNF{mT[ieƜ`gQiʓxiIKn4zO:{R2S4tO6w2RӨ4 o7I,8*MyRvҖnҔ'=ncKɀOҔ'w"wܥ[>uy@x<"mAz@ Ca/}IsH#B=CZbڲ8:veGƂ}~%:Ʊ!ĩe iINi #YS̚Y T#mJqK{t"֢ԭ;w:Ԓ Y(* }CTԍb%xxD_D~><B" 4/Ҹu/rX&m=}Trh Dnɟ_N)Bcpoo_D~PjھH*MhܯҔH_O-*Z.CF7j9D)7OygʾV*Mom·^oҔ[ߡ$Tr޻4Ҕޓ}DS&A}X49t#X-w:-a#Ml.HnƕӲ #dֺg5S~FTzA( qSG;JS7TOPLbZ-إ]hoP2Vl?t$-\(vԾD[iUV`$CC|NB'SnuTjJI#c pY;o8 7C3$y{[ 3ДOQSO?.:&UllڽЙmPUj d^[e&5!ɛ1֕7 3k9V+E}hlHBC[Y 3Jʮ]pCVU wYPhi0Abg-NM*DγOP-P:됒ѨFmswҢ5Z;%;9q`06o)./T抪4qQ#g<}GznAW=o2 FގJp4a1uy=1)vkV|Wv4t=kZ\(6Ѽ%YvQE|gQ8S3oKU[es-k)1|^; 3q|\Dc|2m=#DbAڣ.%ږ- \]MLK g5#mUP ?}>#+tsJT܋D*ABU<uF]*mcMcxD6g(Y_*㇚F^s~reiQ !UT[߬^ѬFo |c.,P F41wjDmf[_Zp&3jV[iS BAp:Q BG[Z{ Ҿ!HjZh`Pk0X@{Dqe-u]Om錂ʕQ%<1Rht4QOz:](L[dGM9XකcLF>y*- v &?[X9Z!yw?2)0,|mqaqHe˧q-Z^]ܱhl9Q5g=>gU4Ւbɫ6/+1C(X5x+5WEX8=۸HzӃЎ Jw62 7TWiC$hI؃+#GZwĚHx36&+Pܺl j >9fAYe[ /S7e { r_:~CF‑=F^̎vDbs-C~t;vCPWu'{fa2%Bp,Bz0TY<\=G­i/m+&M5jXQ'Y˴ Ѥc_V(+ y]EVLq;))52+ᙏB GPbLWf.V,.H 2'3W JBPPlƢxg[Ur/I%GCIrv$׆h%GOIRM.oKI|6IҷJ7JҷH{mXz0O⡱&OGMk~ܵ%lzw^H9zwZ+IuF~0[ M; >s,* *,OH(I$"<s2 d|A.(V89i)܌N?,s=8˝pt5|= W?&ՙ||^Ua]V0權048NGīSӜNP{5n?δL]WNO\WZaH:z\/DIix*)0?nV,^E:Hnv(<xӮM%7\ n"Ӽ&q"u7z>K4MM~S]*ڕUۘ*||iGS'mAdS/o6!_H)?bB+xp% eqx2YGkqZ GummBplqTN8 [gNOmC:x' L0̌ )w5FpK8dH ߙmp9y^3լhTB8D446^PR *gW 2v& W+$%V'9\u۫a{1.]OlǞz.^]6BUa9WLDը|>U3݆ދ%.V4ٳM]Z%rP\Nؤ6Mgm2+Ї>Cy v C ow4zp3bd hmeki K_ɵѮ~EIO$,}_.[_IlԿ%? [2m6hΡ~" sVLԤ?=0 &Psؒփrfغy$9ܻ7gKW &j9 ħ-L665B昱TeXstyclstext.texxO6-q/88]SlƺIeڐItn%iz81}14K`_ݩdi>v'i3IUR #j>?.EUjVBGj\y7"Mb)s@+er#m8H8Q"?|T@$lhhw&8}vA㇚ . h'幰J> )= $W0pEӦAx*OW5Hhh)4S#ytIZPA6z#";g] ˲Pp47Ip&IFF|dD.ˆ/s=.FMYqպ)ݔKә4KAq BnhTo:R,pu8bEWb_. Ew<^TGHUccuIWf2 vDEhȕES;urFݹ$BO!x$J.{֚>it_L{:dn\\WRjs 1ﭭ}eo[0GJ3:\4f<;UܛPU4v(`D*X^#z7v w-t7oqmesaZ\UԡCEDHQt ($])H̠a<_3)<:~УpyjYӑ n\37zezI* YI-f%#cDOmՠ~bCpѳN>m (fD'$ BL _zbYTA޷[we 8M|1O!_Ԡ=ԃ>[0F>t"|¤`I@D➂0A4*B Q G})F A'glrۥ$=@<~YqCD~ϩ2CKjpthۚXz(n?g|TW*]]loT?w}iut\7ZzkJ~j7c_,W=KQ1jix&%6Ԩ.\#fuo=7lo2WpTdžeþIIOvK]x#X~eBv`Vbkw݈ԑs|Ztߌǭҧ_[ۙO>^S6ruݠoL̶4bmW5WgYLs_ٌ 6Ul7q.5ةm*Cyw~lO2?r-~XջM8~_?>ja>lb[?Y`:S}iZl=?3{?v4yvRёjڄH_?viƣc~Oɮ1\]۽iw}E'|\o[5GWeYi$*٢WF0pOȸZSW6A*n0~dT#<걪3rg2.lJMDNJW6|\7/ח>|{LϔGsqC="MX}Km| Ʈ)v?)Yk~m.P*MK9%6gL06߂ W.`LitFRyb9Qy[ eԷ?4r;glпt~n<;Cif(Y9+1`w8_1dίrvSϿX/v&'řdh`q҇hpi?*7s!,dK!ƽ3 tP+UC`^WK~ZS^caOT~q6Dv| u }Djb Gt 4qH}[W4b!TS>hi Dč-pyю 0sR}HYR!8f9xץމ*%CalI Z Ų' #jߍNm_^L$uS $ҡq\Wm(;BPsƒnΖoqq Yr""(N(?BGG&7݅~<i0MC;aN0gRN ΁zGY4hI$cJQ1wnf {2mQNPm.?}Z&[*#n`Y)A՜ ۷"6Y_Y8M& ~݌; QOG XHt/UzC4Ӝ9)c/c###G$b6⺦ D,^T犓kP;ٓK.L=)I9^vX\(5x[,@BL=lK:8(QJUI"  %RR$ iǞ/Ń"4z&xPʼn[L]H"| I2 C+]yHh"5XZdhb&JI*{[֦$3X`ǬޑГpcm &5' u!/D먄z\ ӊ:{lzCj*VXSVR(ANC/kPj(35PZ8|(i -\+tʸQD>r+ eꞌЃbбISO*+IBȚºU@Yco9-U]r:L z[!9bJN9{=XLhbTCV4E"*L,)ZG3N,P\,"Mkm%e,Ƈ(aGZ' 1 .rʞB`90Y{Ku!zhC30(cIkD "*\V0-cvJdB%FooXIlc^'] e{.ܖ?Y{KPȝf0"y9wkS1'Gz&IHhoC S8?gYF}WVהsRFcYEtYMx `<`f}|_0Nh[N!YֶC0L!|e)RHS!4.CeaHR uy Pr4zؙepVv;I]p"$@n~yk,BfkUN CSh'FLBӽJ͊-] -S}=}54VӚVɤ6a)渥?[ە~lH?zUcJ=oo{էprs"m^V6y\bVo#6 oO#=ӭx˰o?~,l> % C7 ;X$v[ɲ Z ӽ1M6aIV<(ɖ-wOheG>{)F>?תM^Κǰ2; ׭<#^y?8?E‚#t_ߛfcZ_w%i<9QI y*s4%>5ыC,8Gd7P"+#@P'dA4!EJו ("q[-H拰/-_孀ѽqI"2/Kپ%1ڄQE\2xXش*(%Jyi51x|8.~q-6eK'$_>w\x D^kU/ŜO4!Lualua source.lua dec_exponent(?:[Ee][-+]?\d*)function_args_begin(?:\(|"|'|\[=*\[|\{)function_assignment_ahead'(?=\s*=\s*function{{identifier_break}})function_call_ahead(?=\s*{{function_args_begin}}) hex_exponent(?:[Pp][-+]?\d*) identifier+(?:(?!{{reserved_word}}){{identifier_raw}})identifier_break(?!{{identifier_char}})identifier_char(?:[A-Za-z0-9_])identifier_raw,(?:{{identifier_start}}{{identifier_char}}*)identifier_start (?:[A-Za-z_]) metamethod(?x:__(?: # special index|newindex|call|tostring|len|i?pairs|gc # math operators |unm|add|sub|mul|i?div|mod|pow|concat # bitwise operators |band|bor|bxor|bnot|shl|shr # comparison |eq|lt|le ){{identifier_break}}) metaproperty,(?:__(?:metatable|mode){{identifier_break}}) reserved_word(?x:(?: and|break|do|elseif|else|end|false|for|function|goto|if|in| local|nil|not|or|repeat|return|then|true|until|while ){{identifier_break}})reserved_word_statementw(?x:(?: and|break|do|elseif|else|end|for|goto|if|in| local|or|repeat|return|then|until|while ){{identifier_break}})xxiufs ,``gaZvY_JZoiF=3 Iô]ߐO/HU?k^-uu̬]J#~~%?iD|N{M; 8&uC L=rB~ެ~=y{_g`TWTVU_VI8_ΩF%pN7+sI8[9߮~4>{x=@#d)}j [Vm{j U[p&W-8߯ڂse~hӱۺ7%~ϧ-\G(@^2 iv*> z" mz`塛iIv8@.g$13A 0J=+t::WG yI`"@Za/H{ p<.VlQ؀GKrCkk#ݴy t/`/I>~\]=eӶ4Hld{%HV1K 5|v=&E 6أKAܐLA2iRpnIsOAe`%/{#)k@]f!`>t~O8L1$Gҝd1EiH%䈠)?McXML҃ƴզJ]Nԣ#&tޔ:=#~gn{sf2I) |j&Q9H̤1L?6)r96<MDA8AHc/m4L5񆧻j-[^XgVkF%L?%,^'\7u0BpH4A#fYɩv%9=.bw}6֡!!䖁"[>Tr84p=݀ⓏIw3/a$V@ZѮ,u׮b } IᮔWjƫ"@o^/F̓,A0m7 eп)rǥ?8}L ])jV- 06Q/ "X{2r։G!10kT',"m_ndҡڙoI‰<%gr 4$?f?]Ƹk;p 6mV5Qoxȵ؋D=J6QH݀^J%ѽy~_w-Iƶn E<ݲvT]y*j6&pF:[vѐl5% L$B4ԀF+9V<'30 zQ2 r&]row%ӏ֮dxTӴ! 0 A10Ճu0e G15˭?E[$%ډ*<'I4ځi{<]q <iV,}YTI.EKHD`b%tZj5ݫƿPӴ"k7-.P7DyC&HOO@'W}@ duhFp6J 2t0ЃT`7*xcj(f.ܒAP0 @һ*$XX(8PsMz!YXq`[H5 xNNVJQ0VG:9ⴲR H]Q+ɧeI$dCdvK>@"Qwg##RG659=WkImBfg&F x%-Ȣ 96Q/%^NNg eLNȆ8_Lxd%A]րJFP{4PEᴺt%%B4?WQHW._d3dXQC[˻46*\G\LϼFZu2Q:WOXiE3`F1~oEюZیMƖZbєloz_ 9kkV]3u !}66 x Ndhr\vuJ m ,{瓌rhn4 :AhD/ڴkQ H!m$ZDϰERyJ_joBZ7Hg;$r6"\*ȉԭHĔ5z9"bRfyblIhIl NI8 $NᲣc%;kʶr?WfUOcaDN% fP agar% Jaef]Ϲ?N]0Z=)BXH;IYكjr3=K*'rC Id#_Tb2/~ֈqP$G*Pv+$zbn^\l)Kʈ00 Om7* ݩ yDSm4I4!aRs6>倎?+IDNm̃1!q}jcW1a^Q -ql$n%>QܻFUR 1 Ρm=ю+w[*[-G5Ҽ$' `yNifa=W4c9\B-nC@/"e N9ȹyrs 9"}R>/bm\J\X}Oqn#gܿ\ô';ەTerb{չG[cgL^sm.7Ή oNtEߚ'5I:`6Sdq't GL{mŽe Rc ?/qn4$](Ke9jJGe^ktQۻ$>ww|cwֶѥ׎'3ۥ[͛S*+WO|==+ P^P:¥.LT48G[sUN2Wե93CIh=Lr/_9j`0P01{ ^\+/\qfا1ҁNx"zgG >ٺ)A,́mRĝ9^J:?3 ̞n1phS8 q1Օ?m~p5MH@II/P"N'ArQB=:1<xTbx'o"o*n:+ɱiYhtJٞW+" sJf\8 0-]W]EPwkųڊټv{]Mm Y7nJ˃Kx~W;Uw_]B9y1z m|WM%JUZ m9UOdk :ezn+7~k_?>Wkm{t2 1Os`*`{]0OA##OcfB$Y. p̥ {# ,.r" }8Gd۷/&&8ma_? wh,Uە1:J+*[ X+AvS`.5eȖ*[œ Make Outputsource.build_outputxڅQ 0lɓ~\!}XM*i~&%sٙd}oLsY2Lyc>Bhj;z=gRs)QX)Nɳvw ~L6´@w! }|ȣ%JP6+̈́K*cFYXf .Xpʛ!InERey0 sںc`A!;`Makefile make GNUmakefilemakefileMakefile makefile.am Makefile.am makefile.in Makefile.in OCamlMakefilemakmksource.makefiler(?xi: ^\#! .* \bmake\b | # shebang ^\# \s* -\*- [^*]* makefile [^*]* -\*- # editorconfig )close\){{nps_unnested}})?first_assign_then_colonW(?x) {{just_eat}} {{varassign}} {{just_eat}} {{ruleassign}} {{just_eat}} function_call_token_begin\$\$?\(include [s-]?includejust_eat(?x)(?: # ignore whitespace in this regex {{open}} # start level 1 __ {{open}} # start level 2 ___/ _____ \__/ / {{open}} # start level 3 is like snek... (by Valerie Haecky) {{open}} # start level 4 {{nps}} # level 4 {{close}} # end level 4 {{close}} # end level 3 {{open}} # start level 3 {{open}} # start level 4 {{nps}} # level 4 {{close}} # end level 4 {{close}} # end level 3 {{nps}} {{close}} # end level 2 {{open}} # start level 2 {{open}} # start level 3 {{open}} # start level 4 {{nps}} # level 4 {{close}} # end level 4 {{nps}} {{close}} # end level 3 {{open}} # start level 3 {{open}} # start level 4 {{nps}} # level 4 {{close}} # end level 4 {{nps}} {{close}} # end level 3 {{open}} # start level 3 {{open}} # start level 4 {{nps}} # level 4 {{close}} # end level 4 {{nps}} {{close}} # end level 3 {{nps}} {{close}} # end level 2 {{nps}} {{close}} # end level 1 |{{nps_unnested}}) nps[^()]*(?=[()]) nps_unnested[^()]*open (?:{{nps}}\(rule_lookahead&{{just_eat}}{{ruleassign}}{{just_eat}} ruleassign:(?!=) shellassign!=startdirective ifn?(def|eq) var_lookahead,(?!{{rule_lookahead}}){{var_lookahead_base}}var_lookahead_base7{{just_eat}}({{varassign}}|{{shellassign}}){{just_eat}} varassign (\?|\+|::?)?= x]z8w!0ܐpd`?8dJ9 19V>$Kd$QRWw-.di󽵖(\{XK@7FhvyQmiqrZ, 9#D{RN~/S` Z\kvJJbNKIZgOY)SDz.ߤH{^+r|tmeǏ3W滠΃xQ:eul+T;vҴ2 ne;nnF0xsGsW{<oj<o u7w1M"8S F5e;Nq %YOI$A zkvlڮ@x~aU[9x_B9B2[v!QkkDlɅ&fqgPeoBPU<14z[M;STγޘ9ת!$߸&4N@d3@ʡ .˔>D?G,HـlctuQC @(k{;~Ю0ڑ0vfxJۇ+Z4pH5SS _N/Mq,hg(ӆDPJļ[3r˰^i.px欦 1܈eV@?D9F)%.U9yɢJj*p0˖!7̒&pr d6UXQ-Nfj #=3ŬĠPCo3l@NW`(b昫¼`bcU%)RwRBعkƚiX55Ʃ"lH)oh+L Y^gw@jP) ntHJ}vIe&ك;5sb#GOK ;޺߱.yAG/S:{ɮ'#~H6τ_]OG#j9>CP)V9r(sF9k? }?ug7}n!>n_?;^ۍӤ?8Luh,5ΚL>#fZwmTYOp}0Zr W_\ 3ppk W=U#lhxPV Q;,v3׸>u(m}qZC5NcbC lx#vq]Ё:Tޫ''%'JI Xɾ<'e|ItD5Ca\@i%[Fϛ gh1ah^$rK:\(S{SSt>+e{-˨諳FaI.K9I6d?ˊe[Eߒt0O5udц¤v̱fJVݰj9{2m:Z[Kr%a5t% iAښnŏ&rA-tRl=f^k= ol3&jJO*Ѵ0XBH FB&CB ɣ (B75Qb򚿔88BPiWn?_a8XyH(&k ]a~WXiw0_~ l.z3NTBIR - :8o Jw eTUzoq_ .fX""EOHu&Ir(Sչڊjo%%ˊ'>2V ,m@wiiwjVė@SDX'fW,ҏz6OpWܾ'U9dby<^9RQ- _<,y cؚctx0 gVnkR W>YT"8KQr &p{+Ι-t{S\5>5mTǐ^^)f-cГ85G |Н'QjlloFLk:.A0Dܡ9K m/RmÑ$'T1pCVުjZv[*̯0 +nKWp_bq]~W_v1']~WXiwQ?{w IN&4})IOȾ 60G tlqZͬx?ן]e_*~E[ljZBk֬c~xqK }#6kXNH?MeMarkdownmdmdownmarkdownmarkdntext.html.markdown ascii_space\t\n\f atx_heading(?:[#]{1,6}\s*) backticks-(?x: (`{4})(?![\s`])(?:[^`]+(?=`)|(?!`{4})`+(?!`))+(`{4})(?!`) # 4 backticks, followed by at least one non whitespace, non backtick character, followed by (less than 4 backticks, or at least one non backtick character) at least once, followed by exactly 4 backticks | (`{3})(?![\s`])(?:[^`]+(?=`)|(?!`{3})`+(?!`))+(`{3})(?!`) # 3 backticks, followed by at least one non whitespace, non backtick character, followed by (less than 3 backticks, or at least one non backtick character) at least once, followed by exactly 3 backticks | (`{2})(?![\s`])(?:[^`]+(?=`)|(?!`{2})`+(?!`))+(`{2})(?!`) # 2 backticks, followed by at least one non whitespace, non backtick character, followed by (less than 2 backticks, or at least one non backtick character) at least once, followed by exactly 2 backticks | (`{1})(?![\s`])(?:[^`]+(?=`)|(?!`{1})`+(?!`))+(`{1})(?!`) # 1 backtick, followed by at least one non whitespace, non backtick character, followed by ( at least one non backtick character) at least once, followed by exactly 1 backtick )balance_square_brackets0(?x: (?: {{escape}}+ # escape characters | [^\[\]`\\]+(?=[\[\]`\\]|$) # anything that isn't a square bracket or a backtick or the start of an escape character | {{backticks}} # inline code | \[(?: # nested square brackets (one level deep) [^\[\]`]+(?=[\[\]`]) # anything that isn't a square bracket or a backtick {{backticks}}? # balanced backticks )*\] # closing square bracket )+ # at least one character )$balance_square_brackets_and_emphasisf(?x: (?: {{escape}}+ # escape characters | [^\[\]`\\_*]+(?=[\[\]`\\_*]|$) # anything that isn't a square bracket, a backtick, the start of an escape character, or an emphasis character | {{backticks}} # inline code | \[(?: # nested square brackets (one level deep) [^\[\]`]+(?=[\[\]`]) # anything that isn't a square bracket or a backtick {{backticks}}? # balanced backticks )*\] # closing square bracket )+ # at least one character )*balance_square_brackets_pipes_and_emphasish(?x: (?: {{escape}}+ # escape characters | [^\[\]`\\_*|]+(?=[\[\]`\\_*|]|$) # anything that isn't a square bracket, a backtick, the start of an escape character, or an emphasis character | {{backticks}} # inline code | \[(?: # nested square brackets (one level deep) [^\[\]`]+(?=[\[\]`]) # anything that isn't a square bracket or a backtick {{backticks}}? # balanced backticks )*\] # closing square bracket )+ # at least one character )balanced_emphasis(?x: \* (?!\*){{balance_square_brackets_and_emphasis}}+\* (?!\*) | \*\* {{balance_square_brackets_and_emphasis}}+\*\* | _ (?!_) {{balance_square_brackets_and_emphasis}}+_ (?!_) | __ {{balance_square_brackets_and_emphasis}}+__ )balanced_table_cell(?x: (?: {{balance_square_brackets_pipes_and_emphasis}} | {{balanced_emphasis}} )+ # at least one character ) block_quote(?:[ ]{,3}>(?:.|$))code_fence_escape(?x: ^ # the beginning of the line [ \t]* ( \2 # the backtick/tilde combination that opened the code fence (?:\3|\4)* # plus optional additional closing characters ) \s*$ # any amount of whitespace until EOL )escape\\[-`*_#+.!(){}\[\]\\>|~]fenced_code_block_startR(?x: ([ \t]*) ( (`){3,} # 3 or more backticks (?![^`]*`) # not followed by any more backticks on the same line | # or (~){3,} # 3 or more tildas (?![^~]*~) # not followed by any more tildas on the same line ) \s* # allow for whitespace between code block start and info string )0fenced_code_block_trailing_infostring_characters(?x: ( \s* # any whitespace, or .. | \s[^`]* # any characters (except backticks), separated by whitespace ... ) $\n? # ... until EOL ) html_entity&([a-zA-Z0-9]+|#\d+|#x\h+); html_tag_block_end_at_blank_line(?x: /? (?i:address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h1|h2|h3|h4|h5|h6|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul) (?:\s|$|/?>) )html_tag_block_end_at_close_tag(?xi: (script|style|pre)\b )html_tag_close_commonmark(?xi: )html_tag_open_commonmark(?xi: < [a-z] # A tag name consists of an ASCII letter [a-z0-9-]* # followed by zero or more ASCII letters, digits, or hyphens (-) (?: # An attribute consists of whitespace, an attribute name, and an optional attribute value specification \s+ [a-z_:] # An attribute name consists of an ASCII letter, _, or : [a-z0-9_.:-]* # followed by zero or more ASCII letters, digits, _, ., :, or - (?: # An attribute value specification consists of optional whitespace, a = character, optional whitespace, and an attribute value \s* = \s* (?: [^ @'=<>`]+ # An unquoted attribute value is a nonempty string of characters not including spaces, ", ', =, <, >, or ` | '[^']*' # A single-quoted attribute value consists of ', zero or more characters not including ', and a final ' | "[^"]*" # A double-quoted attribute value consists of ", zero or more characters not including ", and a final " ) )? )* \s* /? > )indented_code_block (?:[ ]{4}|\t) list_item'(?:[ ]{,3}(?=\d+\.|[*+-])\d*([*+.-])\s)skip_html_tags (?:<[^>]+>)table_first_row(?x: (?:{{balanced_table_cell}}?\|){2} # at least 2 non-escaped pipe chars on the line | (?!\s+\|){{balanced_table_cell}}\|(?!\s+$) # something other than whitespace followed by a pipe char, followed by something other than whitespace and the end of the line )tag_attribute_name_break(?=[{{ascii_space}}=/>}])tag_attribute_name_start(?=[^{{ascii_space}}=/>}])tag_unquoted_attribute_break(?=[{{ascii_space}}}]|/?>)tag_unquoted_attribute_start(?=[^{{ascii_space}}=/>}])thematic_break(?x: [ ]{,3} # between 0 to 3 spaces (?: # followed by one of the following: [-](?:[ ]{,2}[-]){2,} # - a dash, followed by the following at least twice: between 0 to 2 spaces followed by a dash | [*](?:[ ]{,2}[*]){2,} # - a star, followed by the following at least twice: between 0 to 2 spaces followed by a star | [_](?:[ ]{,2}[_]){2,} # - an underscore, followed by the following at least twice: between 0 to 2 spaces followed by an underscore ) [ \t]*$ # followed by any number of tabs or spaces, followed by the end of the line )3-x}{#u'/#K%[u!@ch$Q3CMdV,[Ď 41$5I'ƹ'EĹo>/٪B_ CRnr9:uίfzn vmuwyŒκBf]YbQv0q&Vk4>y{_aVV _uf}0%ӅY_YbVI<fa}(;g}.;y.usw8m5-nIkWinٷ~` v|+z:9ۡI8-. Baq79ՓFVp9L}6'C?~I3ݭվPFҠxtKѷ7;W-#13kioKjٽcLsQf.߷;ؽ=~@_q;JIc/·*4$IDAm 54=g FCۧ~$A%g`,"xNshNY|}<%ߘ,|R4V?F_JBJR/ג<[T7s ?N VX.ZfLV1aLΤ 3VDUi!3otl% 3 D#d?Wۅd/$1c+"(%bơ1!o'fer>k$~((H`?\Js2*ȧ7GZlVjtpojq mJ9j3L'ɑ?0swij}l^niLsB|n]7޿h֨Kf5uY#pF! E&͌r/h]ޢ8^#d+ TVņG`rr\b/gE#dFs]}si_.m >|SkZgIkmE-5 ڶղ~-O(vGkikkޖ)NFJQA?u$4۫"Cw4/_PfZ϶@ v.v`5P>YT/eR2HU4yR)Fo{C:]R Ez,3fЈIu-wOsG=$"`|ƍSm*)XԮz ˆ]_HSs3D:P< >%xd>Kp.5nT/#;zNŬr)] *ztL'B^湴қ)_Sf2?He3R/!>?53͞Aϋv^<բa; +zvK9IYJ`䶃E]4w\hgһ,x`&b|$U|_Lcf^[n,~}zsIO&9.k[Gul",qEFLLyYP`7 1O]r ,g$3Y}NdAY跏/4'57Ӭ]%xj& !Qd VL455:vD29)\u 3 jmoڤmsOI h]Gm-w9%=GաOndsJbvNixFSp&k "V>Y~ Dy0U;&xfSd(3)JIiڡhYĢ|Ԛd[rHa)J:fɄ7!3Hʴ$9O aȉ|$o`n>,AɖU\4.1?coHl~0kɇ᙭&n>ZrGU:{.y@MUw6G۝PMl`3izX)V7ƦfR>2-;o;SAfn6mƒpg\ HF8d:2LT|8Rr0SN% ®{-,b̙uQwg=#JکEOѥ<Gt\A6 ָ ¸ʍJlLwI 8-$kVz.{T9Xq)r8?&yබU0X`-ޯܔܵmu>oEݔci߼8mB<Śz`HqVLFHZh^˸AoӬNljo`]rn#[]y<|PcPFBwᝀe5`?(1SN0Q9pNheQmY3\5]3Ѕ HwYdO+13\5q6-hkGJN^@-@ӯO/ɇÌ\r8&_"QI{"! =&7*agѴӒɳL<Κ؆ob;!N }rbdCuVH>?^޲z (bs =}u7 UYm"$sRXUZLiᔛ0_ g2>,H0TrGr쑉BjxnX#rꍣ<| 1I*(ݮyxr  Jx/+BWjM!єI┹Ig~S[^sU * **F+hRKtjW&FaT5  ˜T@@seeyqw9B z\7_PyV#c(?p\m )J9L0³Y(O3, pBBRmVk4z-; VXP9gkBh7%S 8*O1%3)umnػJ7фqx9O$LB=C1`7;/7.+8G5GN);X_@>l tv5z{S+!_H˖jM & f>iD55>Sݴ8rTW}u6aKeN]TJT2^ǎtiŕ:ͥT:Mk[<])I{=->/շ#&۶N{E[{F8DKcƅF.Ef|{=; ml-褵.YЊ?K*WДʬԯ7\˘7pf=\xfvIe#cc-ˏl! <-- 6uzw{av;=?8VF!vza'm|MAb|Bmy~5vgհv/ݗ JtONT -x)9!T2b$ ]VzQ!8 O?[{aQ!^`r0ԇA`* mnV‡6 ę} 3)WqσΖ8Q Z[!Of#ېBBzdJ])]gŎM;"YOݤϔ;^&2o"ͪE=KCmȁ7j6vN3a"_@~rds&YXgs&w,Ie.9. dOfbRV. oTlsJ2PyLodqQ'fBl[d~BdzɗK+ref,58h,xuM#VO*F}4S{}_7* e kc*,Zhg[, v.v`5P>3)eL =$Rp")MD 6 ms^.)| OcX7FǩF`,l嵐Q.J}B*S7z2$NbKBn(7*%Y7y-ӨdmXhA"S[!{A+wh~Z>'DNqX; N'9ωU'VĉqNsbE ljN]̉l'T= gD1Ò>چPG4T qJkl͆ԦD6p @?@?@?@bېq }ڸcW+<d/(_L%^IX2lf'p86-::::ش~~~`,klb5 xxxx, fM PPPPLco6qow. Q_Q_{'tnSnFn*/~}_J r\G86DE=F^\+w}R>=xFiHȍ)*?W""G(KxXkX#0ŌP=~ g؉K*Q`T^^޿):}AIUZ*ʄ<iaR;©j/#KWtԏo]ߨӨQ7"-*:aCw YU\cܩYgvM~cPH&=;6yzD`ܩU .s *a4u %[cko|pkh>'f7F j ~+1 KHRΎYE7z%#XL4|WƗ&Bmx2-ЙB(/D`Ǐ% 0MB,dלD@ǹ̤ \r;)W*5zyn7IMoS3}7ٵZp,ؐ $S`':d#sX s [_ݨD9q$'1 ~O&m*Έ+1d0_{>ᪿOAςִ*:*V7F!6,JW.o];٪&hή}Q)_I"8duSsv'ثVN5?YKcc[lʯf;ON&<tT%Ryj> zl#KY\?ܠ|:ɆCg܈4|$}P2~Jw2(,;"im-;\gGg`ziѓ\{9ES 񗞓5 P{F5ɚ<<SC~stHInp]Eߦ`^C0F&I6G/\}TKđ,w{dm:~Kޓz;g]:z]?$%K{;(M*Œt4 ޽ӧQT5*R\)իA6)4Nb=å|)ֳ-|}#'DYA2 ޳<0NZ-qit4:3EP#YaŶ0)\i]P^i,zK{CZQ ZuUG|)EUeN6-siHi6x< t4^^8lO'wJފᅴTe1+TGT^v/~nSPV5̛jU.jϘjGjfŬDFbo!c^U*'Kj\4AzH4N$vKoHo ߏ\DB։VQk<_:mBr[|N^]Ҿ|g:d"uNoQeqou5HڨC,>7ȱVze&3v }Zݏ;' bq2!U|!YŸSe%dNBlֹܨaH0Ϛ8Gڰ]l=VΫ+ 4&k㋉rE1 8&"6b= oǯD;x԰a;FU^<&nqOmb+][HZˋ|{3 FNwɂe2ql4Bxeގ!rOR|->'K;E%eЄ].W-6k2 ϧ,)i(9ORai.Dqr0;ȍٌUR/oaW|/9);zf2ZŔj끮A Eݬa6+ج`*VxxxJ?[ГMyo4*̡3tڡ.^??f[R0%;ǒ=ݍZ"%nEI1 l5?x~UEba~B'u yR1e߀{`VD^FcّPa$QqL2f;?H0#鿗'^F36#/2&d~FzQx9rҎd*13^b*Lj-.^40}T^n6jmi+W!ԍWU ?((:}G^0B#p"’԰'"8OYm)v=/ۉF/Ny,%OS|>jlŒ*|NK3,KY9'|㾫B%()(VK|$^h*s -0` L@<@<@6K=Qqy5 uM_H2גh?Am}_7QEr4/_Pex v.v`5(jF fQ̓RIxHER<)MWFV.)|LK"3"!S{1rC`HqN%,n7ǔ6ȚP#OMV\-Æ;>l+\JhD`# 7vwjjAgDmJ&-X`тE ,JbĒa3fY9i۴f X`٦,9999X~~~~'ŚeFld6%@@@@LZ@?@?@?0iI$Mf#ANϻYiDfG8^n%]mT /05bttth^RPPPlZi=M /5 /I &-~~~`Ҟԗ0zI}oG!csCQ0^]ZT 8p9b5qTUOQPn8459DgGn`MFcMĜ=:LJNdt> ٞVHXtVPN_)6u% \J l:d 0`gU%UmPf͍U+z6j~#4v`B+f/\Y?XPxQlG28Wo2>jHx] műd۞2V}jhf4Ia> n˗Esuvh _C_Hz(rZC5 D3aRQ7jdoY 춳崉ED]k7h(bNUkUs VjN#58D`JzKjWIjRKoX &EW! *"|~8HXzA`/DO|K$6{1'☧Um]jW5·q\\nc;g$lCU ÕIr) 82k{(/;ިjۋbۋ"bn`@6bcTp,a!4 ċWAY27뛍F w]geno! -Z,s!3xg|L Y7ZfKɦ0!%"Ӧ*!wRocI ag*4KW XJWpktM jao!nEOo-fJD>|hi)ąbW[njOQNI릤TPs%ުFZu͙Ůg2>G٤9ebۏ}>H}_IE2l؞ۋ@>6G+n0NX\w;GEڎM+@ y'Ovl%{1EKl9۸ #U,9*w*:;:ͳR";c+)a&*q?鏉Ad8 6l`6l`'x000,ZhSTTTlZ@?@?@?ib©O`O`I &-'ԧ 3ԑ ]Ŭ4" $/]mT /05bttth^RPPPlZi=M /5 /I &-~~~`Ҟԗ0zI}oG!cspN5Etؔ7 u pG߳͠ݖ^AXƎtS"Qڿ+~NIX͵B"s/p<Ͷ[ Xݙ |JLûKNzˤd>kΤE5Fq封YCؚImlPk =q|3Yu/=W F“rR*+ַ̼ ӤB?YaN5%_RmF5hc05nW弨25 4dZH;F(q.{O1x4۫4:c*,TӶWHC̓RIC"W-IiJ뷽d 5)|qdHfphU{&[h"`|lES}Ԧ_ MultiMarkdown text.html.markdown.multimarkdown(?i)^format:\s*complete\s*$header((?=[A-Za-z0-9])[\w -]+)(:)0xڝN0 sPc\?_m.h YƗMEZ /!qw|>K%W%*IO뉹1r[okp =B Tm:8Of) /ޤI1"}tSⷆQo ZE=iM ;'6#+й|`F>"Z!ax9jrr|M Xel'ݾN#?Kwynsx꼭z-R+2T!WUb5Y# #sk~&MATLABmatlab source.matlabid [A-Za-z_]\w*axս[$Ǖ&sgvv8n]9$, Y4EhYd[3:Vqɬ d^%ӓ%~Lzѣ}q̪rf8==")5өv0視oK$=8CnK/Gȉ[>áֵ߽$ ۷#]7MwnG.y\߽*˰.0J{߻u)nn\m߿4ۤH&Z͌6h ,k;7 eRI;5ro kW[TwZ?p>v< :Gn>to8&6E}?:zm}T]S\q,( zo ՍۖO:{G? :#RX_SQgp#m.¢6g)g6Z^Zo=hwY~WoNw|֗(N!8h ^gRVWwN;ʾOqY.ta=Yst.wnn^o{绯b<|ү &VJڳz蛡?dj9ۣyzr6w箷_>Zn;즦=hiܘZ{f܉fWpg _`t,LZ Л_F!grjX̞_jy-5ٍ 'ˮer%j<}M6stƹhx.("*0yzs -g'$gG%9[cY|!/+rC %%k~,G wO~>Fn'ǃ2L Z߂t}OPt۵QaA*. 01 U۷}O'-?9=[oϟZ]l][0g4(dG%gzᇟy!{ Oe/c$A/OqKk}oITb^һ}vI znz2}rPn7r<  !ĆyDOI _w(bkϝY>bly`|F1HyW7;x~O:OD gy;yB%\^2ϛt$t@$s`31Rn*)4eO>Gۃ{@ly3?A~^u7|ܼEf\lҗn/1~^t)VSrsz:/t۳?1^ioKK-k7٘1E+/4|wr=IۀniZ@*0i=3!}) ҞTϾfY?IZgd$ALژ$#s6DE;rH Y9VYi<4KIYo@\GtsuG]oGm]eG[tf߱#c u4!\?:"dnCjfߌQ!ztlI2&)W1w!@.G&:6Xom5 ޮa݌.v?wXӜݾ͓9!_fEWC^qu p1Z' B[8FE?5o:hQ   _4L.b|7RPa Fc8r\ho15'x0Ë!g -m C'$ij@ pp-kLՈAWQC- G,ߏ%.[C iCw̍1*&^t\'E8Z`\ט#&G5$&cQGh#uYm ?pkO/D[A# -AOAA xpA/pg[6IjB+(:F.:99ţM=p 4#$낡USR >k^M3lt`Q?`Mtk >YHWvIitU@] Iol1{nOMMwnS 2O_ж~ " S^b]׼(7K+2rD[] TP% tYà>t `Iv0w}K^EIe1,0bdtpۼ D@"ag˞5B A!]&QJWm !Kf\r¡jmXI̥HQP % zKg;R=bBx\:,3-:C}~Ĵ *0+#^`wbmbTyi8ƥ"ƻYc l*)"]r K^gص)ˆ%.dA0Q BEq^ _@Đi\h)C^%m=fVy,jaWwʖlꚵ+bNȣ Cn JVLMRS Gz䋌ɋaQ8r㔛V,P&ZXb(ytbt݅IU;fERCdK"LsSPO4XjQ^Kvvo|(s^xE눕/Ւ RYdK?0ߢ]C؈ 8(⮤!ׂQ0#P-|'Z[:XǶKWQֶ\Jh!]'8rUѢ5E;N4.F ZT."l HHZ]CJǢ$<]U$:,4) B*!D2l Kzªu_5+UymJ<~ޗ\1("#R-1iVK-} 2WܸDHi 7YF t9T;lxbq+"&[jjl\lXZ|@U=GDeC:lr*vF$;",P@uM 9ĕ6Le [J2vyI HuuZfHDj#θ!֦츼{lYKמ0YmtCWi",q ,&nޑ^8_>F 0vX bky{kbDX`]'܈a+fƈ'!V|f #Z|Gosl$3EJ s8tڣ܏X>cB9xD7jEv &"Ph4rDy^KE 3XXI{KѮ2rL2o-ǿ^HWTK~5Y*L6u&q?\_(4ƣPi,l0 "16Gayͱ XriV=Jc9TUf P9\[Um6cE@ $IW>%&X -YTc`2"PZR)9xU}ya)1P;0:by&hiiQۭ^#j8 mb껫M-.܏NKU՝0aHS O}Cзxao*|A=ow_:ٴQG)N}v曾W7}䰉)n݆r@_SA^Fy>C.bFTD/4܃aG31mC0X+ҰN؞BZF8PF\voJA ]; M[ vSPSe)SG&[d!qfo+Ȇ [5 .z=ݰv-v-(Y^Z~^RrelD êD)+F~ ˓zex,'yOٓ o:,^` iEObK4@ЃDh-\">ﵶX`kD @h$j]h O[ATÙ!(McōxfW.x!y%ҁ?=M+VO[! h\ Âk, Bk(_i[PBJ[6>==reo)ܭetu 8v5֖G,ZoHo7Gϩ.X` ;FKz3j~dڱ#8aѠH,F$e h͎rJrĕa3GT*o<{6l›qμ7M?z@I+0 Q&V".2ʱծcz}'PAD|Lf&r+7P>!y|3`|6ӄiƀtkP2詴$1aq!xFvB4k ;} m <2"oH`g +5zȡ=IxK3t#$oBPP@c{)v$$z4{,rӄ$!Uw9XC1B=g>_^ưpPPHYׁ]_#e\7@ \b`Ct/#]ckq<[t1VRxm\# U(ta\TYTLT6=#RC$0t>pzUCV ѡQR^BK^T "u 6OBPuv}d6j 5 @=>H#]UT6HqE.y)f '~Mv*T'фUnQM4JZT|`Z= vmh`eDm\0ᮨq+S <" Ya!rɰKs2t#)ty40 bv@|v+!@ܢ0HM*(qv֖ʡc\gFP-Ygڴ3u&9 $n?Zu]cdtdWGgE'ne:nMJu4 Ǡ0j LzѦf&TB3>E4wHKl|5S}l=m nݩ:=YV@R&,]уxT xnf6X!K˩Jz^ qu,8 YiDؕf1i ,O6$̰#(o0| =|)Nڲ&uAjs<6^@Gvgr2G\zdʩ f`7ԍFY#jmu$%ARr<mحiSM uMTȾiMn[CH}9k-^V֔~UUYP8N"4.Qp> rZ'SA#d'db>x)J0u!v灸l0{/4 2J8źe=n cʗd巾A4^B: e&Md+~=);(gVfԑ<B%X|W?J'ꋆP W!b :0Pk0adiKôMڏf$nZ\&fDјLH83 u-V@{ᨃ y uD"`!uXܹn %=u!/=p:{E&)$`0]OJ|=Wd̗K;<RMI_J n4OI4%LEmcX2aK)j^Mh~o.;_Fet 7 Aʍ|J3nt!z G a\^yZ@9Ywa/,;_벚 ]M 7˄=nU߁ׂzk4GEQ]v~t. 5^6.KmT!=LhGD˱g[W> 1{Wv>8v1yc,MscR'uSuxTGZҌRb9J6!ZT-OܧX9MDR=U*NiWsr ~PSn\("415F Xe ,ya}A`~ mcUyM@̒NpAҸ* a!g|%l#m (HBZH $U]o{=`sCKlcʒoBz1#tQ=\^-ȼ4va#fBS)!0&TiKVMUA@vI645tLb e!Kr9D`恻hדUv޲@]fAn TWv0(fø+u|fK!4)( &"QQqHih!N=Ɍf/OIê( [Ny?rtJUǒ}O0jW, E qam*(E `eלcź{-l'T}龊{馺}Is*pƮ g١XFFxB!_~~TBh$ux49F`qB"G N ))81L 2]P^pQXOl]h9C,<= DNC58Ox9nb{KM]$:lŚϡ8(uE:.K #`ˡ glL \EJփ{q0TaZumS]jϠ'u;أc8օj1hc?>B%pixZ+uX "jp"cq .1/c" 2U#C<Mk2D6vNφD܏ngXRNv6x4QnĘX&ɵx S)bHtGGRJp7r xʼWC l4 u\81 HA&S5O&3%dz4bHLTgSVGX2MLbI4Z3\aq^A_DhM2'͝x06_n, m9P v1 .>)ΝEPOp2~4Xm3sfҵ )}%Z^4bh@5[Mq"jI ;;tr\4YpHcCXB!b*Π"JH{=PIB nF}չ~iqh? O潰~{, W4šl-xR;N| AC92 ZtC%(sO1wXNdWG(ӣYl&W "g9vAdpѕHouN8u-9h Xn8tԭ64(#QtmB6 xzsryمqD]SN.2/:Hcպ˶ډl2k*_}_}a>KȚ4Y3p; s<:")Y}a H"ȠqN]+KtcsiaAK?.ʏDtAi2 d{v]è~څMo܉%Nn>M/=Z̹Ne#W@ha7-OV^'mԏ :w:W7Ґ j_9̌gSj %#ߙ`-#/U(r%Ehj49ٛgfIi21)S dq1g@M>yJO6eϧ$4pKS=D)C/y񹔿rZdd'MsljJ賖5"l2p9Tߣ>=RF5R,$9!‚`ތQ>rJ۟͘C3ި蠟.9kpz4Z\tx-/< <:8|B939Rs"8.I~ˌ><➇x~9wN/(g t4Kxzhoz2{^WjJ1Vxد:ZD]WIDŽ*)eʵ3 G75AVr,< m,y{nb?v: ?i8V6[yݚfK7Mk㨑Sj5mĎnpo7RSw)sJlۼ3A9":8Hh!v(0mr,ΏA6$(:w1[6k"69Jr%ef#2ՈL5ӭ&;;+"2E#4>iKRj"o }x2 St R1/ҾD`w6[h+'VЄ^ DrXrXj'"gD4ЀPwVV-|AE%XcVN, -&:Hkώ+!rux=|GuYKHnX;+98]qr Duʯ @Q/M7j*z~"Œ#!f]LG)KvP:R.Vrmп=|P. PdдWi;S5 HoZnlv`L" a3sx`F]{Bm6B;]#e涄 /"[dokguj"<2W҅ +D4yܿ 7xNrD BD6 cJcPE]}s_ɶ3})q,%ByU_g"nwD u77N7ܑ 0/QKXU2 E*h)0|Bzk[Ô&TSaHdZ%+1X n'Bz Γ\6@!Ns;Y_m0ugе[JFKR>J>:W>pZ|< q./LbU'JBL=OZ%RBB 9{Y CDaq<WnK,:ک|wjΚ<}f'CJ+tu; !d9Y{VzcJ ~wλƻ ]]}W>>>F봭/W?mH GףѾ;mV)Ί9YQ꾒>QGiocD$4$r$$Ҩ1Q1 tC 36~X6dž}kט6 ұp@W̔`L Ʒr(8o~y8 \݊5{ȘfL^4&tp7Oc={K>rF&ÜӛA&!3Ȼ)o@t˧5oȧ}5&s")CŻޗ7~巻5V@&ՓO兂0⑬`]qdw9bg7p,2h~ }tj3"A/C<&ŢptJ4cK;YҵѣN;, (Tt" ެÝ2IrthY93Qc/[Bs$nٝv.8I7_\ѽw}Od7!p'Ǚ/h[9 k!Pw!ߜQAxH0܁33#q%Is*&}/;. 0L7iPi3EQ|MnļW˘9s&ea0h δ."M..|0Y.e,#Ԍ'AGM,kkN䦰MלyttPg.3LE0U>- H.FkѥBdOmb:[ZԍJ㈳KGjo!: YYv+$w[fj!z*jciΫt+SO5wz>f߭mc*]2*'j>w ދB osGwDduIOuSR>K<}ؓ<7YۼV\cw"9߻\#O(2*4ύg}̑e/p腈ɥ՟@# !*k^:gjK$S/V* h .x(; \&ޫ=rOL&?LBw3UsSlmP,䖙Di[:r8y*\V,l >`DFe^˱hf敎0gSƇu/EưW$O)F;-q51W_ddLֹϕ7nC"6DBTXB;ԒaزC hri> PƊEߌmty(CAWoR$Pz/\KBPg]+R4+] y*#&MRnPC5{kUPC%x;b 59%p=L<) N:R~ Xk,֡>ΨԜܧ|$+G;J1pX-bX٪q.mx vjm5ok@8 ƅ0Ty4S3x)H;&tb'k9Vx-1y.stLh[vU050C\Ϛqt>4gԘ5EVeϨ KZM}5 =G^r8F>wCh@:ԿwS l;WpGn+=@G̕KN&Ý{(`;(D' Dt,@tMs-bSP7ec^W܃ 'IQZ~5hbvtܢkx ~1t´bPq!f"Z}Ix| #:"z}ct7L0LQ "޾5hybxl KӇ"z $M032W '?cmw{`ID ӓjg<*Pf{S*u]"Щ;4`^-6Ε{̈uQOd6EMa =7sVUF)Ib)aj%Ĝe3u6r::`2y_c+-?;)#\[(u3%;2E4c!!+<΃Z- ٺXRHV.NF{lחZs w/14~TO2ЩEXJܳӶt-_Ɯo粆jrUW T6s>W|iSΫ2BE31vB27qbWN$PLGҭh!r-e)V ij\4+tLZՁ+# iM+1}A 1~Z%Tc䤙`æUlW{FMCU q(2K^DJh*,4(osMڒ"ғHJ]5ʼnsRvOXm iAiC 6(Į-H#O6!36%hPZndP!U2n2&tE t*aL|(.Ê,+K]9i1pc;nGuƺ{#b; 杼Bp}Bj614l r$pspϲ{F`YB>RV@Q=Dx&~ CkG,5eBDVyMx T%+~yEcd&&^?cL;j_nͱX0"V%@ا$LA?gk$̿*׌]מ:jgzYA,w~&Wla%MA DTӧ-X+hB6!TfusYdXn]4Fh1l E*ZUJΚbb*&tKg!n)`H#7 p8eD$5Nn0<Ԣ:1TɓhE # ({hҌT$P+Kn VV6w@ I,罝b,.WiJCk1„E)ϬJI\ФPR~"{ü@eDog9 xۍ?ԞÇu}χ$9(Ǎ|HG)DcN,W? |GrXz@,ve[Ӡ$'fye`عj{@l-FN݄(5lɹ]ƇdEФ{.yp3K8}Y = >Ij{5 sGl4 [ojnFѻNxte>v2}a~9aJ/j@ Zڸ ےƶe"J3OP_o%`꾏$b(*׳ԋTdv- DuCw[w̘F-{oE2C[宏m<?[tp#~eE_9[EawLN٬wZ쇑n 7ߑ7DzgUq@fwlLcv.h9-Ǐᰦ<*mW{[Y.\庤AewuAߦs\۴+z {}"\oCӁ}_ۆ~||PIV*V?5yq V o4/Rhn wFR1>;ALǫ%i"R>5gW|,Sz0Ε)i nR)pU|Tz\3[I[$94Eڗ:dS2_bZfnd'MQEsvҀlB "йh 2P\C= fG#H!B0͢ s%}c|;>蛝}@0lw8 aPT`:'Jm ~QbĚ .,liT)VY jg>i^ HؤNP׮6UcIb;>4.l|ϲV,+> ]A \ז}: xyhJρ6L HlUUٻ #KZb ޘP* ziUlFEm$!xJE~sXUiUQ#209c V(:_$i\8GyTA6r@# ]uN&${$ ,\*َ Jıf ³DhC "W:9T&ߴf<݉lI<3Vs]Ǥ<~dN]Z,d)XDbggτd<H{A_\x4NeϹV/NfwT9/p[sDSGU}핼;t 5HsеP3BM1g:3BJRSs* q~kFb~z (EmՑIYPI=uY, a9BtUiq'XJdv:FbXWkhibWd#t58T&B]/hWy(+A?guԞ촋d#}怴;/43HS$j:-UEDLʼn$DliV#k3"1L}M~XhVn7%9+s yy !sdό>9?37P>e6~S*Q&t_0ͣ iK):|jWvJvQw)I{-çC|7q:%M|(ӧU9A&z"rd$Ӕ]˂AAy\}\ "V0S'Dȥf75C(KVZ5v;sGDC`^fhp2N?ZY+ޠ^sFOi/VmzΡv|"E..ʸQYq |0 "2C\A9f() 3s' }Rg}`N$Lj1`6`Y.W7e"OPXg@f硣@ƺáӷСbOxެc:Gƈm!NjjʃXEG[?c5fz@cJm~G`'1]+j|_ jU:[?3D>kg: A ܦIϋ=C10$Uls) bF Ҋ N,u5 :,w"1x {w%aֹ#!hJ?|A264d+^X#T: Qd,g*P(0-'qj+FaQB0N$tΕA2HM@ƤC<+i&uQE_v}uP¢gز&Fj?wam6+ IAWR^]5"f1wtE`mCt@iOa8 dO\@ EVJ)^;`@!`rPF>\%}tD݃PKI lzgis Y+FٜILr8_js21!9L8%^H<+UWYE&y^M[<:vWd.<CUǖ~V4[Ljrȶ;i,FZI)*OIjve]= Kz")y` T96)M f@e-LӘ)HC VR-P6m#7WJv=H@/m״ F4pc gMmmRq@7* !$t(#`EȐVU4L!#ަUC:Wqz3lG'iJn3\ZJu:~<6plnqu7|H`mUw _`O[7gVGh#"/W%nB XՀ.=a9va[J"(-h!+0I:8H*"|!_B[6_BYvǮ|Ǯ`̂rF-`M[X{\g O;T551mifzAn͈kC'bAhUk8Ւ8l"7LM: qgwÕnգJp."c.UZ\x}C_t8꬛]k&ykk&hL}!Uk 2O\1 -\a+E7֬f@?l(͚bn-fEHH^!d 5k3#V8sΐ{qaJv ϖNo,ޮE:TYn_`MSw 6gim?DF?kitӧ~ 쬴Ǥqs@OgZPރ@5UȏOBsih+SZa"CYU^wX?.C! ^1sD 2=+\Y}z;^ E+[Jn5LʤV&2ũBy텨}_T.t5 Rr*$6@#{V"73ԸSJ TzӈG˕eNXV-iiOz`)Մ*%:⦫?mɃw|'eV\@cn:Si5;ּ~j"lBToNpoCbigZcfhl 1S h&( )Xэ:^WB \W2~X0K$4G]zP̼(_͕OS1+M>KbD"Tz e\喢0'j&*@|p :q`k>r>j8-yB;+i6dJYe ǑٵU:gfC;@qG͓%nqdA%[{% [ݑli4mPj{@=֝QI@ De!y*햌$/(ԕ!IAJEby|Rpv>QےӡȧU ׵5k0k/k.k- ?FDXЇKF # Baڅ$UtqD DF;QL1bTuH)л$$eaQ}ȓfPL(Qz cS8L 0"}Mwa.dFu(ba#h F@T޼3ER$D\5\kk\@}pn#PumjKα9-t.h &40 kA2P|}PYKbҳGE1w!XCs  i3!KV&dä"BJDw&Pw67xٖtcՍ'1+-f`m,Kːw Mƭw۽ \eϏ77^?gp~ HFs/Md춱%B6C;8:k<ms6 gi|`bfW|Vdad yr,Edc$$j/yᾡҩ964rt"]^8Jpgځdwsk =>tw53Xop &ZlExz`L;G܂ 0;ض݂_We{m"q}V;aܶ3AMO-궥aBlǞKE jgcݚAr<*W;nj݈C 3ڑSeEhlC#r} 4a q0c=DxtgC9!oC-4]ɳ1"FM2/@.A=Hie kQ?wpN%8Nu &xp'W;?3=yVcQ,Y NV_ֽ7p9HC" M8s`k7;_=?{<'_}$yǯ=֗㟞'_9w9@+wow߽1~g/yx>^:>_pk3O zph ~f1mkKݪɳG8v; yvw1Bn"z!r/W׾g{؁ Zx9}Ͽt%~el_>@SS4f$c!5/֖rF;,[O|s;on׀Moo[w4S/!:;"FԘoka_>Q[N{V+{oqvTB!?ĬL=ș޶n?sKgM7o䁯Ѓ@(+[}>?ď[y V>j{OCamlmlmli source.ocaml bin_digit[01_] bin_integer(?:[01]{{bin_digit}}*) capIdentifier[A-Z][a-zA-Z0-9'_]* dec_digit[0-9_] dec_exponent(?:[Ee]{{exponent}}) dec_integer(?:[0-9]{{dec_digit}}*)exponent[-+]??(_?){{dec_integer}} hex_digit[\h_] hex_exponent(?:[Pp]{{exponent}}) hex_integer(?:\h{{hex_digit}}*) identifier[a-z][a-zA-Z0-9'_]* oct_digit[0-7_] oct_integer(?:[0-7]{{oct_digit}}*)sign(?:-|\b)suffix(?:[lLn]|(?!\.))\b x<ْܸ}<gF9FR&DJH65en-F}}#  X}tD_$22 t򯏊ߛ~~GK'(L8SO(?&~0kz=% t] ~ 75@O(5@R5@Ow%JU@JzzN>K@L*jT>ccaLø1 = zz]&O, Ҙ3 d<g~6OV3x& | ^9,K]N88ñiKʘýF7id2272%62M12u Lm SӧmiF?^Η2ԑ4]sWϣ$9q@DGVHZG>0~K >F_p(iN9\Ro%GS$R+Ͽh`RϋߝILw,dO% - l, ,tI"x#]q2r!躨r0JFNf 屹,)p%l[.5Iq2HXY'}9󏏜O7Faf;7%TIM?wȟRWy>AK[B{@Y+ A[+2C#ǽձYwmO6oi GdL4,XSawRenOE'C^!W%HG28}i#$k A+\k%*u:I3J1cUa*鐗^*p,lۂ@vdVeA)FxdHak)PQMVXVfJif%Zp#+1$u$%$|GCbxC(ͫM&m |; RB>g] iD |We72:~+Kq5B|dycʏl_̸7*_X.olHzF/\9׻6pMSϖTc;u:8xC"n)f|'QwCTDRvHͺF >pU `-+L65molJH# Qf^PuKLnT+^t r0ţMp#,>HtM*,N!&09{2Iv:͡ 0 RU-kgӍz) 4 9 ]=_t5_BAM?3}d$p|w}a wͮ&6dȌ?.\2aov[eH׈b2G(_k4iWZp:Ai:Af>U ܪ ST{(8 A-Wgו|QPw0LgYD޲‚4]C|n _Oz<zu203{F%9+M*BekU|7M.?V\[=dr']p4-.@p`"JzZrlbZTk̑alkx\o07E{@sfBRIDGUT ۈZvCFr5N+C̝KKb4jc?iAwwڪᧉkY(ukT;WpKS Ҿ;?aU=n/ʭЭRoRwU1?x u "AK{ճ]c#0rېAςPLyq|E2_֦RIE;U˥T8\2)KXin E^ճ{+c ރNDurE,SnCrJLǂ)~@.p{ td?.JJDgr99Y 4ttӽ*p}Vp2 yAWcxf/t{)Tav%G %c>遮sW8Ya]z[=Aֆy1[6;mU%jDYLcЖWQ%aqV?ěx- Pla-_+ 8؉3Eh.UhZ9~Kg kַ\c!K]zuh f=~o:ϕ{x+axoړ$TGN9݈SyH:DV$ՎE] ¤CGx!~Ґ5f4Jݗ{?n{N}4ۭ["ql1Վ ʂ먿Umv: 㠕 -ND78UH%h^8n^񞑏aXM\r(&P@vI:HR"'pE_:q,@N`|@䞥5\ A%Hxj. }YoEqa 3D&l Rr9؊9PsKoFy(7{’+c* ߩȂO De\j*Y67o&*sϼ<uJd*!!xӤQ]qGlmOy:}Bu7nĿ+G6`fkt' 4qk你qs0DZeRd5-<늽;I5?KCni& [K!u7j|RR>OCamllexmllsource.ocamllex[xڭXn6&ݥMnV ے)grY5Xf`?g:-юPtL\d#))ؔ\C?ꬮb|JX&Iœӎ|=Ĝ0(Kn1m=I. [4˔zeN@f,s 2XIK{AF7.1uqD JWLre W@^.xp.ITh"]EF ^?,GJ} JiB+CߧWAvCPUt0q2'IIKgbIr tI1DQW+F\%X iW[x[&a:iPx _Gg/ퟏc6'cb7MY0[:Ԅ˴NрYMs.u,qM^|ɋ -yB_ޒ= ϸ1>Mluԧ>8{fY )@SN*MWóܷ0SgVM5*=emD=t\Gv5 ]@owK?!Cąg :SJَOXkwثB}k&A.I<'HFas6nS-ft^CWJǬ9F{8K_պYQXrMY+EL\x3̉slxv1¤ nۿmP&z8Xjl=;x̉oL61a9rv;MWW(^jZ.]Lo PtcS:f5̟= qD7K6ܼlMxK81aQ%WAb1x 5Œto 3aUSxJWep?ÇB7t)'#:/łSPmsk5wu"jr?dU? OCamlyaccmlysource.ocamlyaccxXn6%n&Y֦iVF$R,ִY?{N@K-DO>l/'uLR V6m|ƏzTˮ) <qt;ї@[,Wǔ - v@lM![&#J(c2&>-Fkċ{A9a,F1c32N"|/&qpI+2Q*&c1 oT3`P0jBHXTX!:s?9r , `Y5BA1.AEq%o >.šO_A m#*Xiҫ-h1~c3^~8쁽0>lCP ?F0), ԄfMopqp }HK> * jڅwL_j韋QxH¹chJ$ov ͍bo#8qj20[x}Xd ao٬AunJQ(EL[lcm6ʵwٯ%gvasms-zN& @WbN j05#[з8ICu{{regular}}{{paren_open}}{{regular}}{{paren_close}}{{regular}} basic_typeseasm|__asm__|auto|bool|_Bool|char|_Complex|double|float|_Imaginary|int|long|short|signed|unsigned|void before_tag2struct|union|enum\s+class|enum\s+struct|enum|classcasts4const_cast|dynamic_cast|reinterpret_cast|static_castcompiler_directive'inline|restrict|__restrict__|__restrictcontrol_keywords[break|case|catch|continue|default|do|else|for|goto|if|_Pragma|return|switch|throw|try|while&data_structures_forward_decl_lookahead`(\s+{{macro_identifier}})*\s*(:\s*({{path_lookahead}}|{{visibility_modifiers}}|,|\s|<[^;]*>)+)?;declspec%__declspec\(\s*\w+(?:\([^)]+\))?\s*\) generic_close>)?)?generic_lookaheadi<{{generic_open}}{{generic_open}}{{regular}}{{generic_close}}\s*{{generic_close}}{{balance_parentheses}}> generic_open(?:{{regular_plus}}(?:< identifier\b[[:alpha:]_][[:alnum:]_]*\bmacro_identifier)\b[[:upper:]_][[:upper:][:digit:]_]{2,}\bmemory_operators new|delete modifiers={{storage_classes}}|{{type_qualifier}}|{{compiler_directive}}non_angle_brackets (?=<<|<=)non_func_keywordsaif|for|switch|while|decltype|sizeof|__declspec|__attribute__|typeid|alignof|alignas|static_assertoperator_keywordsEand|and_eq|bitand|bitor|compl|not|not_eq|or|or_eq|xor|xor_eq|noexceptoperator_method_nameq\boperator\s*(?:[-+*/%ˆ&|~!=<>]|[-+*/%^&|=!<>]=|<<=?|>>=?|&&|\|\||\+\+|--|,|->\*?|\(\)|\[\]|""\s*{{identifier}})other_keywordsmtypedef|nullptr|{{visibility_modifiers}}|static_assert|sizeof|using|typeid|alignof|alignas|namespace|template paren_close\))? paren_open(?:\(path_lookaheadC(?:::\s*)?(?:{{identifier}}\s*::\s*)*(?:template\s+)?{{identifier}}regular[^(){}&;*^%=<>-]* regular_plus[^(){}&;*^%=<>-]+storage_classesBstatic|export|extern|friend|explicit|virtual|register|thread_localtype_qualifier)const|constexpr|mutable|typename|volatilevisibility_modifiersprivate|protected|public&x=yE՝$4af73 {dQoD"$&3لt‡( *7^7}_UwUuUuc7a'lԫzի^{U'GuZ6UT\Q,t[ͩg;BR^UZFJacn%WZn XI-B(G R#+nNU땤AWqZO" b-b"B'g]dWQR>H_+κ͹b6O5Tdo5Td>[sgxsB-yRK9W= 狕c-3IFpufF=vw$dx=[JI^e P ev5U\mW\Q5-e<@-ٶL`lv$Kh#5*ziW%FwӖ?\0-t-3)`$H PxĈӖu;ImBwmpU;h4A#)44 aCb]FʌeCLjg43[ Nv 1vu#m-R͡`IrRn0ZgYr Zb UQ}z'!b=X#HA_Ma{PZMM!E ZR5R葂]G5X,F_'cy sk?Z9$G( s i1بɹ j6 o$z"cP7I3-[r W |#)i^M-Ϣ=ɞK4Z6ip.ZkA#2Hn?(!&xhػTKCG5r}4 w?* Ct}L ѨJN+U̡4YiD&瓒SA њuIía!',7Iy hoIY[QVQwn)3Vq|fwKb.QJ=HδOʇjm4&#RCZ:PQT'ހ)W)ay[Ngic._VE:(/+ɵڻ&eviљ 4uٿө`>+)ǫ DʳTՑبk[lYF2Ϥ)iKBJ-\}3Bf_^/o\}P;)࿔|voYJJ;´ڿ$iWa9bq||d@~?[/tvk| lH,h/*%x8l mҍAcO ,23hYCY~KefJ_DnI$_]Rv)?J%M GQ]ubF G H3I_٘Sl0r$Eijg7S/Qa5$7TюV&hex<(@JlߙHcw _7g DmbH:A<7`N!LCo}{tY#KQmt+ p4 pz;YΗ٫ݔ6f."nU/^'Un$7wj}>6#MkǶl7kПe a_@paК$042hmR,l׮@s>3߬G.*Ʃp{ּ{u!-ҳ_Ѝhp}a?m,YU 8,z: B ˺[g)]hHkWs u>G"`\X'_aal$k6>t/p㊄ʪ.&|b,hs\l=E)u{mې>+[|RUpw-l4LI8oqkɩ\_P  tVh{>Ff(=sӨV$\Hu]&~cǖ.v!E[ZsLtIn\ Azvzh4~ 116Vu$o;tZlyaIhͮ" hPz="K0R l;I0IRz dДӖ )Fbn]6•A%I1`:A6+ၰ[auX.j' n+@ot([x|ѓs!ET,{cd|HwnW*_zcF>%0 t|dk/zDD:\$TЀ= ڌ:ӬXtE=Z~U:(gX?dIn+Z fV@1_ PhP6(g Z|UWzŝj3~p\AoJ6 =fq-oUDNGPh・=lm/F2vE%Oڻ:Y2)'؝t?/cSr|XX%P7b!EqqQ=I҅m׋2zc; Ŷ2-~˾gRncIt府qt2^? {(sO4jd!C9jAπS$cs2tV kq{Ork{c+- j43LVO+"и=K}LΓ7f¬e"KyyhpuVmfZILi$]ZR.*v4ghntTLlyW9%IZUqXkT0$I4%iz06Vi: a3Y? X UpG!c܍a?*0ױ##CQ0Nw/_W74D^\fl{t^L:ܡ+ mS@"{?Dt"sKytY[8ѼwI49N4/*̂½JEH,ωe#zKl(˻)i}%J'KU-w. gsO:|I8)ɽqz]>й_#\ᜉ|ůBR{0^:cX k:uB~Y:>IW߳)_p{ 1M9zm3 /rC&‹Fͷx>fep4w+HhH^Qd> p 7FYBRWyJӎe&֌e~cHRUY/Lfɍa# O"lŻuti699Igu b k,VE b`IaA(AAvN= u#_툅­@ fR`:{=˅/4=|[t[-Ln&; DYـNSXۘ̀pt۫dYopT&i2lEWf˽|!x3Z,I*]IU7H=4aHO u双Xs-ē }+xb\.'Ц5A sC6C0CCҁohcC[Ѭ*j<7拍J▫9v0St!5aV]#{-2@&+{0P'4wV}N;ݰWhxtp; hwOjmק~]qBB[uߛ.BmyJ[=0^m&hC4àCcs>UAGYDl;AE)vQC\q{X$occظ +Ռ|QREDr-T>#Le/-36Om؆{EB)k=R<a{&w߆ѡ#9wGCL}1Ğ[ueЌ.5¾~l`$Im6t .U$zcةT O(Qjªt;i[%0zaZo4UވY`d^/gЛv,kL(X/7ڳ| 05p#=&ֶkRpޖ]^݁ҕy͜^PSjO NJ? ^XSݾ1(.Y*Qc~/m>O*OdzAjqVDl?"xn@ G5ZsQwZŔx2w൳o=)$)7x4 #w#^&xbɬ OMaܜs~Np=amT7&)v~ nnd@t0@c?R%eBL3W[T_"=Wxah[ҹOċH`a\%5ǎ 2A^C6Ax$`V'<)c˷"HȼӁ <)?Zz+U]7p{"ʡ_hR@zyXq1e$kYgfXF'Rnbdq{*cq6SxDxHj 7t'wpC!lcC/U$dȍkl)Rw8)j~X!z9|4 LЎ Ťj2wkH9pzKw+׫xtK!A:oS{41oX+HCr5S]Ue!߿~xioܡgγNZlк!p:$(+giN30;°Pe}|1 \/BN'I*ų cԷ; 3IwO5,lI5P: yFfQ{O7U _ZP󨘖wjbj)Il} K.u0@Kڥ1 Y$fa*6^֙0N%2UN\htG9͒[S!J:Nf'ӄ2TBxcqj=RY,;Ro26'<:v'Id2u I^cuL^x*Ex^|ƒ[rZ X~Fte'̖8p׀7t{ͺ՟]\>)v5_Yƛ ܇4.䯌XZF,|z rŕ2|DNb"LNFK|C3Sk¬(rb-.MFcFy Ĺ* yYd ӼRaæ{aYD&@x ˜|I8"Hc>W =%?)R9 وyHAr&~ޚh"ĸ-.DM!I3~3ǟԖX! 5LpѾOk J#&35Ul^rdUP8`Ĥ+5,zfY"F.Lº Ix՛3͡H aF~ Q-4 /waďU1 !<Ղst%#Fw฼B^bh@Y=)F_h)µrby|ِDmM,Nqv$nfMNSׯYtVLm J6x\nY0}‡FE- û|uYpS("H wSOk`QWE4107!!_r]o |9Bv#t r#/zQ\ 2F#usN3㻔ٵ]{l[b'ba,.pR2W8)/t}\i 7 Objective-Cmh source.objc before_tagstruct|union|enumcommon_protocols\bNS(GlyphStorage|M(utableCopying|enuItem)|C(hangeSpelling|o(ding|pying|lorPicking(Custom|Default)))|T(oolbarItemValidations|ext(Input|AttachmentCell))|I(nputServ(iceProvider|erMouseTracker)|gnoreMisspelledWords)|Obj(CTypeSerializationCallBack|ect)|D(ecimalNumberBehaviors|raggingInfo)|U(serInterfaceValidations|RL(HandleClient|DownloadDelegate|ProtocolClient|AuthenticationChallengeSender))|Validated(ToobarItem|UserInterfaceItem)|Locking)\bcontrol_keywordsKbreak|case|continue|default|do|else|for|goto|if|_Pragma|return|switch|while identifier\b[[:alpha:]_][[:alnum:]_]*\bnon_func_keywords<if|for|switch|while|decltype|sizeof|__declspec|__attribute__Rx} u ˲dJA%HA^ ,w),6=C,&ȝ؎ǎs_wTUWt`)af^z{w3Y|ɽ8ݿg_6}Yl/M |e)n??_@^1J^پG3+g"Q&YlDE&+hfNWOZ;|QLJ_G8.jv ??o,ퟖ/|ns0 Yc5ʓqƳP/Mmӟ_4/OӬ d @il;m쫟lke7Z}oԶzN|*Gg}yvn_DH?\~KM}0wl#OǞkzH2+"jeAhY4G-Ud2Qlɓc(]?I\(9pF\+`طver[?.z sK=pݷјI67oOid}ˆ4z6^( "ژ[S~tl{QYm:i]~[G1euvO+$ɏˤo$ƛomx3~6$I_&O4=3X]Lgd3(g2Ng:M< 3 0h.>~m|f<"L1444 WFɴӟ_oi4*JF0=t3'dNhJ7iK <ݓsx#Nf<irIc2#M4@U lnOc~u9O-K#c$|izT1FCi/`?Lj\Ciy՞E? *1V.M("-_y cT$1܈lOڠi/۠Ug66j4=WY ̓fW7oMaO n.}t7LC<Ϻ6 ПMxj}?\xԟ͍ ôR1]F5V݈?qEHO[מxq}o=M[,ƴ>/dwUsi=56Ga!vӓ/w7c9$pg8L ]̿ ]!CW`ݽ#rILO\6AGK|wcG4{eQџQm#5ae;QqfDx𧓏4+zqs=ke3^S#;̅,+:4bU/+HsV 4ϹpUqt/5Ls(N P/_-Xg]DȁsrCٔ/rK[)%EpA "^b)V494YV4@y䍠o(jsvRwn< xi4R l8]9O; : /83&VyS/8u5%riHFKnO[twwGOg~\3o47#XԊFedv t4g^h " |za'@aL\PM:\YY/H_-LYmD3w*xB~XJE=!gΥZ0 oԂ2 ]l޸Ț`*.449p@Ρ蓣)KbzQAXK2Dɥ<;o!-dj}tMx8W+DNc]Zw 4ϫÜˤr'I &v?=S3l& ?m$*Do*1#+8%n.u;yԄiOVs"?_l\[. /EfwC٧'Ic9 #B {|j%.+^NlQ#ŮFk&&Wv}_5j6i\-pts54,و# ]VZrX!ompS+ C?n7J˷Қ)ybi٣_A@*Lzi_ꃣK⥾^S5E}l>kmCfl8~*|JiҙpO1ɝm?ZK4JR[g/LGKqyTT_ }&roje kd͑HC'~%|r9dT R/(;>y0?BJ 4iLj):O~>JѧOGq}3_j(җJ4^_\V[;js6|mZ8=!'FOW߲6Sxo/R Ak몳;W{+WյNW/?<^Bw&qikQ+7ZZ\/+;++j};KM:ze0Kbo_uYQkkYqiòZ핝B]5?qF0\l(XGT/XG80(C4$SOE*}0pJr o e ƶsXz&^̍ꚣ1Ctս wfRdLQ"A2\~%w+5WzK4:W||$^> THlK;X7QA5{5w \7_)z˜akBH3g-1u !TGf.$VbI\g=1}~6(qi{ LʴRsGAcI6>hЪנ0J :Mӛ.Ah,'gW$"#ޠ+P5}§BBԯ5 3N~~lt_| ϴ_j3 fJbaòx jLLLP4+O!ŶƏ=eB6!|Ɉ:w>DG9G/!|׏7!_=Bdp#Όgƈ޴r#J~|(p(؃DJPPGԳz%/o%J]߇gpo0&!3?\u 9{6N5o>cYh`8T;&)}9΢B*T 8B*IAcWT%8V-{$B&&b!>hm2Tu1WعV\27a'ԆB$@v2F*4ێzXPjKA"M0~A& 2WkpTX!R%Yx##vE}m\v)-> ƮnDy;X%P[{P09 Բ&U[Qf+`-Ou[2[3Aw0F$N+/p(0yJ-Tv^ NE2iHOB;̿]PdAI8kSqG2qaESv8S`cusiE֏7/zq6+g+{)X}liE9|Ft9}D5IpT1/P]D}Mp yGq/lZyVryvGg9"iy:zb_lk)M4 zffYv~7`M) qM̙x\NM)cx4ο¢\?<ӔG_܆- 3j"lX>0ՖT&[* ֊m7}ݻg|QfpѤuWzYv~ߩ)/A"V:np= #Se=+kx^FʔWfSVO3uNE6@}*D(;TLJ&X҆1''Gzc~l '2?I+³ֱF!gyb61r6b#(6>/Jd?  mܴ?~3O5xK`2 ,bC)C+@WM(`_i<q: EVطΗO͸FMv{)pwZ9,PyB%&9٩&rā7>`XjKʾZ.2rΟ XV0L(x8,PK~K/˜:32@_x)UCt 9IӢ$F7Gxer{ŹB Ң}=bS#e^a; //bx+, oT_~"8Od"&d4:?;Q2q#Mz0FJ__i}hL*psxJS2CG{6 r[w[n^CQ׹䉾Zu mǖls4#rFdwþɤ)m Hǐ܉ J 8DKYUWa=,IX;G@~D`(O*"\y/H:!N0b_X)p5(// ãg~ó-(!&&$RǩHr=]Y`dlP$ H_o鮢kv}PKyf{ZbYU(_P%DNIjX #= _AFAK>GYνvBq"H(E'pwk qΰ!hpL|aRKVDWW7l,Ny*kUS VK[~^f8W|ӎD[rgwrzezS 8e l.: qW)_OakJt&sW&4ܲ$W{bz fcXJ '=@ R.En(0v/ cfu6|33lZ)rrv 1"Mq3I) Yu6 @ a\V0]R_ȵ1kJl <+!!8ҭpa2-Ud)兀ydqxMo7t/I+'$ n[׭!A>".̌L@~cΑD!XyEKl9@c?"UG<ghԽֆ[t<"p.AB;YF ޻"JLt@7YBMñ1V g`;k}|pqPh1U k[nBy۞ rkr B[F4s eeS8Kgsg,ԩ+P eUd$oRX7B~7+¼A5P X]6/3BDc܉dFGD`Jb*Ff+{Y覱j<8Tbl*- J($aSH+rF44_K4 [IBˣ44tM(Y3XfG>P&v 18 dqݗ'O+GLD200߈Ά|")9 A tQ))bW3X^] B lJ`oopMb'QJ|a6:ngX pAaN/ ȵpB#яt؅ip鮊~ua&"C]u-K`V2[:yb'Wcc \ػ(CQN+?A*1 kSdL1Uea!F6;x6"l3-#7P;%YI3KOm.H(OH=$BK!f/YY+YXt(|y p4DZ4W@-dGd{c  3 <nXCq-G#W$U0"G 6x*fzܻ*fmM(y!]D,ylD@[-j".UBd2#,ԅ~e 4Am}AFJݱF=nj0, Ƣ"?bY _2eگ]A89"'bJB0l džZvjzYbG,avu{u–Yڎ4 ]Op3 ! )g.[ख़)YـCʝh@ cߙ`k ]&)$y$} &Qu bO>B^m9V=t y&uy$c(F +W(!Xt=x@MQv c/ 7ўd9P<r`"IkЏRBZ#YtX[nb@+-0pY2,,tľm  {uTJ 31G4^ V>uczk5,VЯ~bP4]ĝM곕mrdDr?DWK Ի$~lY␥[@dY j7 $ FN(Er?hQPM%:@-}Hl QXv~wۏ@CVT9#Xo|UEНO@^)zgM N H_HߗMĻbc./k|WP^}*tt~n\|gl*ō BؙE o}y$ paSOqkZ;d'+iS  n=KfF$.AuV&rԍ֭j_L @#hz fpa 6 +0*ȋ:8=~@tUt~ĺNŐI'${J`n).`Sx*oោJT Xq+NT^AC ==YᾺ f5QJ fX١,JWOR0IYDj"q7(*kaN~X#Qۣ4h5ceQ&#wp!%E<X v07`0g=6 u/"MmG!ضA&g@E[Jx#_%.1 4vS +@{.uGZ0PxMUlނ1A"WQy/Q'Q+;bA6 nY!3AA裐Xڷb Ϲ6a"Eڣ C BiPPEbı ́l^gwHu'Em^cKܧ[4PC@ eg5AL7-]*b3U mB^*טDiS66Tաnc},zVT}RKr^ T󃈀Zi#\Ro#!8A9ÞB3=Cm߈d\P4ij`Ocܾ}h>/ `|SޤbX 2_to!ㅗ#`D.ȋښO5- .qaL>`h9Öp&ig;F;|JP| ccP38I0c$!D@{G5% 1XZbCc.HZmBx+r?"qngaPh$`Sqvط|\BaoDq8%-լ/Y0b@Lj3`Ty3d<ܽS^aFU%_ h6&.2gT@}!܈y911K(D5-K f֊PU% oPz:NV߸l%{[9񪴳n].= #P)VP`C } 0Svcd2/ƽy+ȍɬPt 9`qݚzqc'PkdP`t0Mba@]HH@켍 \JoyKKeù΍EfP7B)o, sG@x@ ZXPb~iBsg:+ھ 3ya1)8~xwkGJm"Z m0<|mbdcm .siDp\[Hu5VtRo!&C)D(n/rc  +2u1k!s==v9&OYQM]*6 $%$#RWqE0Àn4`AxK&#ZI2K/n`* x,t:3aX}bch3#WqB|Hr8(c2>Zk(-Z"q b͐(i"{b@claKۡD쬘FhV,/<T]sQ w%E}2t`{&^C$E@zgο4$囨{7Lr)M  L%"p\@$.'*Ty8HcO tjr#Y%t[jxٻzݏU}.rE2Pq déWW7iiOQAnc'Db$G,hIJ{F&Ϙ@KMQ0բ. fF_<2j T¡`rMMYU)T6xv ;g/$҆ג EKR9 ch*Zcik_!>ɋll]јt:/Ma,7ō- VXBSb WT99o{u@{Re<-t +X)6Óp_j6tChI xҋpIX^ 3#U#!Yv4 tJƨ;re6ւ8  օNj"$*̪*Nu'8/Cu&+Bzb42JXy'ŒR;ŨGq\. o @1rE sL, B'IyKaV5}$>rsQ!냥^7V)faqQ §~2Zvid~E{)8<՞*U?2+CHNZy! ɍmx*Bɭ7tEmg;G*U$pjP jN J}ЫuKu[ўbӳ )'mLm?i3n'=Zoclo Y2AZB x YZR7`E`[k%E"JeJ/捣MDe/@YI+;Ą:ϩ ,9ٛSF,Uf fXTD ie Ti#U Y!eO1T,|AτY2C qIONxJ}t^k &7 ,BJ.P{ ^`";й=Kxh-h;a pmpA~ay6jh$ꄵ))#1;PN1\%yG-Tr`_zɆ9x%QcƄXw 0p "-ttbGR1JAi'\(@QװlALi<݅ē!|$ea'Q=kklJ0JmdhH=l.m%CQ-o@ճ۶ĀCZbv藴F0ݬT/:݌;̢p|h@h*FXg3i6ډ,f Z9:!MIcK@ 8o؋b}N,Du#d(+2$܃xJ%Xg6 ƥ(jKX(Lan Бk D!HO_)!o g)hڡpa NFYt_C3U\8P Ncn&aEHvY8_7Y-:U]s ؗ[:([L2;5uFN z"(号GpCL d-c Rrzw@*`!+O5cph&`WZ%mM s{}) AX(/um6t*<(U7$E)+}$ʟK*۰$7S?24G2Rtۅ)ђ,O7. o̓b͑ 6Bqho/XH,^v?pUkM6߫0ᖝ%!D!Qj#!0.]'QSʋתZ[>u8Ju2d)ͲѰ([%LCxL(-uh7ˍl +Knn ѯE7{T_ ;#o$&VRGi涝I:gcZK3q!PLYAb_"D)'(Z.Ⴉ+޷ l&!ֹNl" 39Q*TlƜ DH+Ko{Pݎ W{ɯ7nޠeoM+\ƕus4 S ~'H"U3RVrnDZ3#Lur K/_mH~W5᧚xbz^@SFZbN'i:06>3}``ә}`xYx1ջ<"?Mz C,A5NC"a(r**>^0[m$X0R hd#1 enJ1T*&IƼ,Or2aZˁ'0w2@8OHT*# PGTPoqXaDT :2u}YFoN^|e'Ep2ʰTR!8MZPp(tݨ T+PVlb c1sz"pA+sF7c$# +z9%u8%D6eʼx&Ktbr0]@ּ"z0Ʋ_q}at`&Abr:z$~sx=ڪariS{-[k. p _JA;MFjlU&Jq<@Aqn4'V$gD^N1,j,fRzngEop[7)Akzsܐs-{a( eemM!, GRGHBwYuPtU07gyJ p]*lJ [}XBkc@-j>*96mmDpQ)+A<8j|ғtc&]geFg+F|Q@@t]Eح.AqiOkĨ\&&D?q>@;swnNE .PebQs;RL敩ԅksZdz /$ W #uqfcYB/aaXƘ1h0dDJO=lmHTjC` X^́Aq:ªt"vB98ΆF#N{)Ȁ¢-NՌ2Ζ67A3ER!d]Y9-Bd̏7gqԒ꩜$`JUWra5HOq0SDԵٵ!jɜ 㫇(,LFœy84u1  43ΝEY:=őa`QZ Fy:Tr3蠡S\LSIvQ} GRxdV-8mS]|Dt[N/2֌0TD$-3 ͅyT$:ϐm̸Sx\B[E:%;mEBĆK+T( -3SH0Z!c BSH`4*B-0[%Z]F01Db*l"+&(VRoxV&nK,Wƾ1u1V qYZ2o'$ >*K:)k=ue)Щ&&0+=)9s ʧfn`Ii+÷i hS #XiIt>4u1tGSq?\+K}',dBTлc9)eSrBfr2tV6u\úC(uL 1^dN݀aH 5ul222ԛ| MH$*rz,( &2C N=M)P]j1<5zwzQf|r+vą !5P`0up@@0Z`y4ÂQ.DNƂiaL*+?JҮ̉G <8!C' CXBGŠF  km, JXm^=@W^J`0RM:0DjoET+Ӂ-룰d*/ M',kHD.D&*j<$ R!))0c'B\chg>{Z+i*Zp,K\Hto@PۉY1Lyo0 8v3m>1R*2x2< "V Q6W61S,I`hTbx me ,U}#(@2=51`2UHoy0@`X5UơP|5XB B#%+-J|OT/ nQ==w6F'&`H76i`wq[]b#$RB)\t7=*x7&qhNz9Acٶ#Rֱ ZS=/ ad5P9՜bRQAbǛUp7r}QV1Ufι7G%Pnښ) ʃ8>xq i-8>ażUk'ix'(7ƖP{\,czHRvt"ssE驦:C[jx19s<R k̲߳՗7?> lI[_ NeU9&I<:pۅ}za?B4 xBvE;qd&V43>ؕf&ht"F<h Ń }d0^)| C8mp<=mcPEGLa3`ȴ}<PS(_`RC #%`?i>`3G`~+xgc&8 Ⱦj8FZc8ǓA={-χxy~/DSG/7<떾*kqѪlxẜ{DsW6 훔9Ŭgijƿ/#f-ua:KnFݫW@J{G!(M 9/"3IM;п<4l+T+c%ՁrDnjL '~M?pit{SP"ՄT&*VKz o)GFxtB!F=| K޽s$vEPz`&TX_pcWϹCވ!s2Ԁ'V#70 9l ׋5yt+m p CȬxX%(SFRE/^Qj5>3sF9f:(hlfewws~BICR:zQ/~Y`7=9 R^!MLgTGjl)ɕ&פFY;3%}g?e9ew/<W6,g&}~33BDh.\>˵F %ETc*(=JV/T`B&T%P{ N3Ǧkݷ*{4X&0)MT[v9=ggfZ`,qۂk ]UoKv*\|w#XXX>KSzX\}7<=>M 3<e`6&Wߟ7\w /'uM! tCL7QmO5xf7{NO~Bo ;ՙUH;)g\-cFQQ/&8->cAnX.+t?܆5bMG10Ŵ`b9tڌbDq0:`[&#L;~<^~r[y۷s3gd{.Ĺ(ٹ|{gmo76Wv T: }Va:‚5{Z* _pF~aHX7_S! I~$0QE?-bpu!RY2;$N)v6\brrDB!%N-0=+| .+`vI>r֗:릷65Ûu%o=*GcYׯC_[ZtjmQXZWu(ƶixq G'e5$Ϩ*+ ՂXFACEMw-ak;C>Z{AC^ư *)'lyԳitnWb)JAyrF}n/B*"]YK'r17R˷ݒR.pK'*iuZG/~ÏfJ')*ߡ5]fx(\Q RYz~ٽ ^wW|ɾ0ߠ%ƳIPsSvje(u9zۍz2 PHP Source source.php identifier%\b{{identifier_start}}[[:alnum:]_]*\bidentifier_start [[:alpha:]_]path$\\?({{identifier}}\\)*{{identifier}} sql_indicator9\s*(?:SELECT|INSERT|UPDATE|DELETE|CREATE|REPLACE|ALTER)\b}x[cǙ&$шh")jIjjP`"XSp@{߽ݙٝݙݰ~~_ogsPfS 62O?3?/ӝq2n4& C^2!;-eB^1!8-&'+9!aHMaM7ij1I&nΝ~bwA|g( s$1WVlc~}!\!m.U8LɃ촶ZϢF׋VyAQOS{nu\,td8gFncM69y.OFλhy2ʟXf|AN*VĦr{OlhKl8ʯ~C2 \?=-BZN i{[[A)dnIcg-76jol;c]MAOol~տ9m oeg ZIv< KғQt⇍Z vq}Db9PNeW.X~uMSΌ$:3\Li0 ʯQuITobL R0nx|?yɯQg&&ߗ_IG׈a0 b ioIзrwAsh`Mߕj^B_˿ Z~?O&U4;,7A}&近 +OΓ̗(=K-Xh)F2]0Lԯ bln?c =sx cWok^b՟֮>n=qgR3,9 [ӈ"Sc?V]Xrh oLޫ?5 .t/8s|"7㫹e,RЅWAӘ+7X٪eEMF:ucAE "A uK L;(l5յk0C,/Y A(]χC0C#Պr6W ʮ~Ƃ8M,,6_{ԃHфMK3t1W솺Ċ(m|U^4ojn ^M{Yy~_Y_1޳ uf Pͼw2`'g@h.FBnj(KEIN" |L.<ƬuZߋ&h/IAE=1 @Sh5GKKSjlq~F^`'Ǭі'k.e/t orV^>+ސvoKVR۬o{֍vZ^ZnzCnJmGoREZU,Wj4ʍu/fQlsYnBb'2Ei;w[F޻^1ikJު4 X5[P4׾^9b^ۮ촷|(CYϷJQ;WW+䅱F `[fլljԂv<׬TGX&;S V+f 5dF6čU-, @&XinzV`*Sm{ 4l\-eljרCsQn -*,'XW1Pcy ~nvv˘_a fK+5i&~qWݲ m6nRiԫU o7`;%`ٲCt|g˳V9&VejuJTC5JI")H寃PbC.4C&z8gDH X YXmj(97q9YiQjn'ayUN@7X(W91$MXLJi<0dRds鰽.]1rV[8+ YkA(i Π5 ݓ7}jU!.H kmT- \&mvÁ^O;y<*R.kP۰LReZZdل #!OSBoVPu*> wa %pLfNM xݯ ݓn lPW<`d%t;Z^խ5=L|&,6om(e͸jp:tr˵} 3'uhev*ub@ &*DXאg@U(MZ(X!XH+{7T &ąNF ~Y*?A^B&*V $(Y*ZBY m\SbkLnp_cךlݪo]{A|oَ݉g^ Prwv zmw-XҲ͔WX=>"X=ZG)fI2"U/} KиD<5JG UP~e򹇓X܆ز~[1dZjԏ>a,2l4ip\LWY@Uk,sβ~(TKU;[Mj[Ď"lT-\6[~ޠ\ N{aږ(UPkջ~XS-)ޭ4q4m3y?@. co܋G σe:2yy@M3rN ٴQkK۽60)0OY{c=Y)f. o6Xg]ЪdVv+~:5&4t1b_o(j[bnu^$+V3i` 971L#x5}]=DCXХ8Qb(yշHaD%C) Kt r2J4kpvoc=@o 9ob^1`1rLgW!+5(@l?`"ྚ7&'vrύz.Y10@k²}; jEjEz]" { ٦ks*w挷\ ˍmT? 1P._1@'jQ6I62tߐwVڣr7mlM-qh􁩓xʃ"Χ7*0 ]:?PTj(`YX'm{[ FFT뙰dJp,,Ә*iO&`'cGsㅡdj)ր)XsrqUIU@ٖ5UM K1Cf\ Jg4bTLC l}’ͻC)P۬2:K:?C/P5mi0.ʻ{: 6XplMjI7J ]-pEHnfoeGoh~ZZle4y"-KBlmK]-|wɞU뷃H"U!,rf 5 Q5|R ړ %"O-ƍ͛ܽɅhR  0~deJ 0pvphH ,V5 }+$X[2vK;zwSVwECy0劵͂l 1a&>AYZ\rb}:(倍NNp^Ci *-!k2T;@bـHXYc1Suy-%40쯑TDD-5n7lg(o*0 >Q]>AݵWE[eLٱ e['VqZk_ѢGC@T0Oo_{! ʶ>^߻۫5an.|zlvܹxPZ3YݲnV[4VLX3e"bO8Ҩ_o)Zon H{AÇ{$(d6 +;?0JRAjfCab^bcap`C76E"ӾK/gW ~uVu2 O]蔐qtdnC::ܿlUZ--Fnm36aY`.\7\[Rr 5֍bn tc 46vA"f6(79Nݝ*I# BvܪڪVd.<@;qeKA%<$B>1<@I]-n65):S -aTYE<juFxZsSfe(L:m\6 ld6Z\m$G#ⱉr. | #!!-Qjk7A#*p;< U*+* ?wY275m_B7CV"QR+ЫkP5+MU뵭RWCx9*Æ5!/{}UF`5 y)3+ߦ V^nvtS;-8:QÙW.[zyE׭i|NľS/?/\znw^T>/Y7dD%Έ+]%Mk6u{~ʒW^yQ|JZy÷*;l(17*1ݻ7@AA y}P;((oD):@\j E߃U_0?65)*cx⮫=Ir.e|Xi0@p!q%-^CL{lx77-it6Ij78 m5HSXïgG/ U p?@#-CӬc[ih)b#o_q %R$oCﵒ_Aq|xoswKllVj cBhSp )&KNx:?6A>jiv~;00jļ^dVP0qǺ3bPehʇlh'BG/˰P1@:.(Q]Mkh'(v(p`D"0f]fL jރZWàeoZw)wl]a%PNTny\l&EJm>!7XaF: @yUjaK' S49c#`j/op3hq ?+b'qd2bL#8 rެio/ nCd.t ö- f!{3wLܵfj> ʪs>ԂiEz(4ܸJ:T&(JW7қ7]ٮ{tM]Luh77`OMh-)|Q9'yĆZ-6"(cLw4- E9#6 VΉ1ثbk-C0~B^P;HEFMnz%%7h{A6+q=OO|Ŏ5ђrVHb7^D]E&!,12x4|Qqdt e DKNa `6(b]Ц-Ruَ ~ܺ$wrJwl ]W Kw5*m]hsT ^$:6$HQ t] ݪׯӕa2SlX`Q HjB`=q,WYo;jdמ[k[~#BA!sf&xmUY9䱎Rbrf/P1ˊj Y#K"RiN,\AW}Ʊڡ%j'{svɖPF`8IlZDnJw ХJ/EmRm =-4p x#!+!UU3=HvjCNm;R#B<*-'A1Y#q+J HoKz5kM>Ow`,4_#7wf1D HăW~{n.Ok[0=^ލi>"'ͺМ%%Ec-9LfU~Tb͆h 9eߦ 脆 $(m2dW'&M.RٗAVa~+l^"Sz N aM7fɽhRY<ῤ?Fļ?8C*q>S!ް0O2ڵ*IJ~-yڽ%/ϸ% ޥ1GCxw‡F:uP_Ѕ{y?Z]?Z/ZO<p/UNk;uY$4KXKCάy]zze[gl꭯glock,0B/CEǽhbF Ҭ2v0km֗gepL4ofY6©l^ZիK^|oyMlkW?v )ϯQ@`촢aS}GYO}>=D+j+IS]^M 3OFP\@735O9ְ3GrO F?>w~ϝ;m*;id`ZEY[ڲY{ˮ3բy딙鴢}e#.\~yn83Aug2}}Dz;fg}fqx YZvmYyZ]~ \-GӫGIWzSSEZN-rw.`Ք {F|Fk;h2;Y_ TO7ߒ_#,\7f-_}` %,_+YecmF$[:M0'|k(iJF#"VSZTտU']Szz3#{vitKu ʕeJeCDie:]6:y ^+}63RzQ)5-[xMot痠rWuA|5h-w˯6U[TXKܗhkI[io+Ʋ%qqj+Vgy]DҭRsQk} 9_+K<MH~Bny;5 34kPRT2xCǾѼxI7²x ,2i25fz%fo/?r^A\]6/AIe^G {Rc^XS~-_?4svIK>Nr9;_˟YL$Ӛ= gx>Kʠp > }Eh~9gw氛K1c=HlRZATglY fd L"i*۫UdlWmm5KNZ;m™:Cž7\md}YG< ~+ckݙNya7lX7gh<@Nw݋f s. O`>Z9U2*[jWe֩GeQ4!՟'EkE-콢BY<ȣ$-OٛeS3)%hSG&k"irN=С;K/a[vƙ:l:VbJ֝x6Hdpxj}g.4+}>F?#SkoF(^oy:d㉂|PvRP7=+lfFcquC_tPP+BŦ^>b/^Y+Ԋ=*H "1~8J᫃LJ&OZ*@`yԢSl7 čq39N+1|~hΦ^9Ю) wG3zaΝ>>XՅԅ<M>Fa]@ux6'WmŰ_e(p!:$vd:W4ICM!OYb83G-E7+^ ;,st>wE6MoתiMySo{V˽F?kmO@HZ01 #%$;p֜ (NU41Dv#@(`gt5IW9si-^|aL8]3 Iz*{PS*tV H )Ak6lEМMH8BkNO,A;(^ -N@d*8*8 e C0=D4bNl1m4Nס Q `Cm`*D7!wluøۮl] mƍ1f2(Ba{viޏhh:8`Tqh<ihUMz@Y됻ѨP>hMAn2$z0𪢬A77հP!hOYjҥOui2@~vf:<3L[18щe7hlmؙv&Gܟ; ߏ)zdYz!33ZeXUxGkjs& ¸h! DNunUkJ/H$X,df3n>5?yU U27/-;/.('m?p?*.|g8QH!mOvLO1SƘeC;} y+|a,l@Cс?x6 ,J!l!^m'0_[q4/|A&J^^VI =UXLȈ|be<&AL6eס,1ѸD'EG|ȸ_vD0A`XǼ(Ow4%J9L_QaY917\YC*UtA 1CǼW1 Y̦'4yۯoz{ZG-f?*e zO=)Hʳ= HnC.7+==|sZkO>湂sv::޻/qwFݢ^[:Arx♏.k|r|WQkoyAႍsb*E;kQ[.x,mC-kEJ)+ɌuŸ́? ahОPFV Ĥ`#=RؓxN  O=`9οϻ.3_ĵ-vC YB`n KH6OšpxGyΏRu ]|oqXl,~_ / +i]љ1s +E|l諭vuuXx/־NѰ Hʧ ?*[U^$KZ~#ȗd{q,5n&>J6@fcW\ G0e2a;Xzz;a^ Moc*uhÁ㫯W '?S,~rza CZf^־@qLgzINnl).^)긫|ooqy3üMJZl(ppZ1ά_-JTBW|]W %G**0Z4nxxk5Al_tw=h[_+.9wCI>r)~X2#Z $=6LE/yOsW_sDžpLzP0V?c7{SSOFSd+E[\.m:%-;9}%=d>"#K|0_xɬ=d^γȒY%|4VVev9m^=qe >W)r{TX>7?{Pb:ƿe39[.{.ҽ}:7_[]UV|Uқu z!6;̅EDžS?pjq'a'I+tzgz {Aykʯx5f|S!8xgpνOA^W,3$VT{l>fm6 :cS&Vo_;YbM{~[ls$d}S{7o]p᫬>\b}i<܋>Wڋϝ[8|<0^͘S 灚_ + T{i#)kC1尺v0YwQ/uKAt/= We$}|:/]rEpY#XG쁫o2'hF1FwpXތo[r6hu"FiBLe~*s03 6؃NLf}P=˺Ftz pXvz7%6ͅc.4>OģoVLg!~^h'-<#|3>Кtkb!Q=s;sIHC}xj_9}(by={*S?* _Cb,~/P5Rklk[_ 6O4Ϛ * 3a?5bPb<FL@{+e/&?}-zIel6I-kO1쫦s8YQg$(x4[Q'Βon\}ni㧠j_>W[P8ˆfw`{o7bSϽ`Z׎їlsYՒ59mƁ:k>q/ L5hdzH&'xp4[_hg?|d9oikhF bY݃,ҵΊF ~z1Z_^H>hʲҦ M>5ut,m",-$iWbUUh[{Mq@??KUAſ $xP Ji>`>]4O J7 2ŧR)|:{~ -IBh )Ji\0؋]0uHu!wEو Lv&FQ?ūs8WtiBN|֊YZ7y!a3C!|e1['NjRR!ZT7c%/FxsלXfъ`˹~M$=%aG͟=[=<rܒ(gMZ9oTګ sW"}aӕQwd3/%xO{t,D_ Dg}s]"W._ ewWc%߷6Lmy=G%hQnȶ|&CvsؙFrtj.(>tQA'>9)^ &aAEU%gvQL,yHɆ.h:{.crKĜSځ %`3==/;3e\F$ 'x" H]B,Ƴ4~7J2$$=_=H}p"lpFfFh:Ù|ܢt!FWxs^Ɯm|}+7IaK |A4kwQBf3; 5ƤhyLb1%aU޽vz2unI(ZH"R>JYՂ0O'iX)s@3NLhEԘlq: )n'a2œCyWBV{#Tg9|,` xS(wad4kȕy8䱴.(2#f+Mw/,"qK娕obLQwB9ގ{D&QJx}B؏~}>"9JHqI*@h v8MFBYpIaxmVKv]̐ T_ɜƲA`NxZ q[ Bvt=6;$~&W%R !NLH [mCM !|}M~؍!#\lX$_V+~ ?4L=|!jq_# +iLr'h\ma2mKg{8C_a/$aQ0?b*HD4J!DG)i&RR7p&sʰk Gzqk(X g S lMR?47P{^]ˣ@S\Cda)>hE *]h*q2]it hɌ:P +Zt2\[o30C0$I jrԱTo<-Տ$057G&X"/8D\1%9]ˎmfg&` Ay] 3/ Ǵb'74ZӋDEq $> ]nD"xp4R7J qAkN&3$nB>=- @AUm#9y)Cpe4,Ze A4ph!5p,oy1?F`;dvO X3LŒR!"Px4N..n4 >Mcτa"߃aZ`dތt/6{ĸMstNsX|v]XƤ!Mۼu 4!;&[\Ew'\f `>3A`$caqA2sD۱~ +"aaM$y@@el"NT-hD ܃@ȉrJ&`][iVAaSǪ0|1HE EMǘ~G}AwXtx1MgGӄd!c/;p Rdgȍڛ^C\ZAO2(9D4RCT'=COqK.c< -#@14œƌ1X=ǴyX3%1DZI(dj'1|Y1Z ^YdP};ti}Vv` <vsP.녓鉁OŕԔ2vpHKyFVV Zwj[KU2yB1cl*<McnRd^y0 l̹4ӤàM4hJ+=Q>UL2wP<u6ڃˆ"@To89CF0X7 ;$qcp7 ,(#Iɘ^Yأ^FT@JQfwWLnGGHfIjE6t&w[YC{ mqYWIPByQWm輪l&N&#0(pt6_-L[@"FH[4Mj254sB&٥vԤe[>t[ͩDm\՚ƜDʿ`ƥb: ͉Oh,}gޱ)laf|`DcOn[';3B r_2hF;bcidM]uf]x|E a0,? 4 c"D#1Ra7R8?QN H*KD ~w? g"Okӽh'ܣ .,D m!BGf o1y&wL>I>GSuĢ$aJ*FH(ň˒q? Hгi%?d}ͣHƇ= Ơl ] g,`)-i'̬p:vA33Va IE8hzefo' ̘oLGP>YNCcҝG+S8 0]03 =KF-H*SfHpeHZ+AIBMSFٶX1ҐV${OzQ˃(ڧ/BE"-mcÅ.)NPfh .Y2'+ݦ'jf)o; Pg"IOO ħ3P0ƒIJ h7hB0YN.DPwGE7p'n߇ڬ C6+]ٓK lk;|Nc +D(YИ(G:Ųߊу=Q.F\lF(~saU K8`3%يp>S)ruIׂ/r ؑ( V H?g(4Nô;ZYn0t,xu.T/iTf|2ՇQ ÑKX>]dd=0Ξ>6;+ydzEh0Cb-<ǩ9=OΠ7\ >{g𴈯 ܃vMvɷGaG@ (uBHEk0x80*MQ7|s Ķ.$H .^[>r.%AG +Ae;4' uAȒ>$h,<)Qvg(woD 'c3w&W^yt*e n쒡 j +' idq!$1O*[r|y~86<N"q>Ǔ!ntb F&N|^x9yNQ2cz{ &Y`{;NY2!^VC_pdϖt G1#C!D` *6n McK;,T`[<㎈Jy[p[*zÀA]:;Ca"gdł?wOEbyD' 8R|lI :#CG"J>赧>1Gؾ*Q2nWH8I"{ 1[#M)7ހ{:  ;1'dFBKh g~qB~ 2~}ȑ`R.f)eEDEeP+VwJ#<uf'eסXO!u1o an[S.#4+ޭcf6;|䛉YM|KaPNym FpR6 ^fqr 95&0LK+[- R.aHW|0=${\IX [leNWI N6n#?k=6A ô?"pW+|o%KQ~"Eji$AOϳc e QwKW`.݅6A64HeͲv=6 zK-Tp)um^q1}GĞ|ORJ)i<%LP Acwpv#K4)[>G8oXl=YTs >qv`%5;ko%2F^~#l,{tN: ;BEC!182_|78Bg$glU4IЯ&΁32 `&̘WAA̘,a=iҍ|1RLP7<FqWR#W^B){\6[5s?0e2H:l{]YH5m *[ 7; ' 3K3SX>yc'DGv!/ٞLpnejs0_5;oa{'l"x2?'7hFP9TQRY חʝj Je˞a/P tFl\#).iv}{){(|V-d񝞑eQBFMz>S~XYa/7%QH{T[*B3;A+{ ]Q;MhV&U [9@2d_+{Y Dw²WqG3hL;3?I<# Y]vk~`XvZ*lBa$S S9ɣAv9 OyHҵJpR8N]gDJtT_ к b.x|ЅodPMN("3= W#SilR dzhe { ⶍU8B*m Î7(=ATw0j׫UPxN2-D:Ўk`ܸF`BD^xeP|%|u,s N`9+'ɂhg v\W ;Fw'>ҙІ,0=!"!I}Э&WGi0\*zxnfZ|Ru$矣.X8ܞʔ^J6x4\I4SP9Dbx]'ۼ0g/g%,HqMNy,.wPO=Fy49 #`*)\jnR>_ỄA&<60ĥ'90ilu#Vab3GxE'7jYj)I$ ½OO,ŷl[7׳W&AE!&I[Ci(<coSmMQA{|<"*հCu~| LuuE[ Ρ s􆊒,F =Xfg)sClZB1Hsl{<:ǐo'hnw#MNNQ:йVp&t0P0ˮ ji{υmy(۽4ґM{\,vQTԋ^lOj_&uu;`ɞ6@ TʅEv M՛lIb)B$&αߑLTz%\R`xmVZ=~Z撂Qfr k$iBSs 9X.-+)lw +H#Ѡ1NĀ:ɱE lbIL,@1(#NYR0(۸tl]ZzBi/č!O@ޛ -e^[s!消5%DmץBeTC:Hӟɕ,nY'V 7) 2@vARtW  '^t=~>ؚt{6QP8L# &TSx< r EiiԦ -I07`x7(J,+V(HbˊZ[<)>|pD!q|4wcA7,]f VW@ ¨X8ʛVXAPodD(|昮$ѕi6Le0nhܧFki`ʯJ* ORE&A~с$b Ht `|*5;$(0liGTD309w'.4(4(TՑN}rejWq[WSCb5>鎴0ݚMդ5yiL>WjEgCV{%fC#7^(nWܲ@- cCk[4{Bpe[&pEn6>dy7O= i,h E˹ C[+L6 ʁH03\ %A|$ )M221-r" -HCy5.)3?. s;63cs|zf7id,f^g;:!XD3:i[?(z bÎQ+,&(S`7rLMvҙy|P0 {/Xa_%X&#5y* PX Mhq7caAh9Ӆ(ۚs1CQ2ng/7@eZf^[4[+.sn}p)0Y6lSweۗ|gy^x&(Y M1$ If4GO!f|Ъ3DrĖ|/}/zw de*hGC/Xλ|/EC& \tm0 p;:6j[$sQn q:^sGu~Rc0V< >ksFKYltE-Rע#WXqy TH4@ct큇S';=Bo:Q z##`̏ w4߳/G/N:рa~GCjڻr~L/?{^DBu>ш t^w9Ew%uyzC 6V a|]fHx !m$jz;Y`~ aG(va-F_br0vjr z${^= ´"L;œ]#4JhuxzמzO\6n&i}VA̦^7-*wms%F7t@%Yc/rֈ̩b})L-4s3S9I" . EpHf"'р&/]@<ȈC mOnfl*Hf ȴA9D.2hu$4?ic2$iFtdc)B̾ h .` Ge;4E@ji'+Mad&Dh7'2Y K=lqskUO 4!iԉ&h<E|#+?tDfy#F9@aūXayjAUϳ_;9SgQ8.d 3Nz {[H_ 8nU=hkq-h\'9KAOm;#@ I?a,Mbq jZ#$}&DH,}$yǬabM>'frR siB`^/ ZFl|7ِ,'xnyxCޞ_Gd=l% S[TE]eyy%|͓ggftP㢋s--[Cal SEs{+-:,L]N΂c+w(dg bdWÜnTNCF@YEd5޴ sKôw,&z:ȧwo<J4LU 5JApəI"ď!]}7Jh]Q; Em7+`!y]|v431ļlEqoܸ6xrY6疭w^gN%n\ibW Fr-i?cKeb.E( 0ʭ?zO|㷓f C1|? #gpvi>jdpo>g9~GkϬ׭{i+-hr6e[؃s̩_l5 z}5kk7&U~_(]-TQ|{>atKJgkJy]rkWofk}}љ/z93Q5& ۧs QqKީz٩֯E{\h i6+zxb>Jœ*XM_YEK9l`} ~1zqw4}1?Tv/B[~[?hק#PHPphpphp3php4php5php7phpsphptphtml embedding.php^(#!.*[^-]php[0-9]?|<\?php)\bIxڵS]O0-mMc%T@/tҰ%z_N==r;Em) ΢8-ЇA,xw[[\?qxwf#vDnOH=WSzzMWi5MrMKxoĸ #¢Fvĸ |GJ/2hD4! F6ŝ;-c0hF"!=w=7BJ !q^{b4/Gsz- IkϡŽz&:+C1hhbC8P#4w2rķWF"$Ы"\?D1o!e1ƴW*Iv>l* W1}Nٲ``.*UրUKܟ$Dp4$p"|6{/9\ +ƆWsK%Gk4|eCr|x=PǕ !㘾H3ã]/eP,(!ְ!_UEߔIU<#xv#mIr茞SF̗BA*:ZK?k%_#S* T߯bpw@+2[c#捤r/5j2>BZ,9Ib,q[ o:ƮAv=(ǯ34H_["O #N K9|_}ceI 29cӔTLz =wiάGLj?o*ʨ=c8zx4|8 nxRx~7/O'齗qB)T 1R)_IHI&Yv 8$״Պ;8Όg73p~>&.ӋA|yNy~<8Z,@Hp QD8cf̤LDJ !:le{3}G %vD=Z(7LNokZ2%\(K^ ;ޠujjr*QTvK W+5MlWG~@%UΙf O;jh|QrvW;?h+RżmN 7\gac=tRN aqzW_qL/Ku#\9HdZ2C2K8%N p1JpLv"U$׽.S4f[̔ .)L`4TPU[&%ΔUsB+5+Z.9e+Va߮%)Y .nP hWPk%"ق: $(E QI2KM+_FKoȈB-V3] <]3W%YsiHvfn@a D5UX *KNUXY4`xaI0,a'>> dm*V^/]1)djl΃xI->ADI،ɯNv>>cЛ?kSI~f32铞@vLWf|`Ov߻/vy>\ǥbRpl6z1 18T7G0 )8'$5 |v%e*>+̑k76)?,\Pgxql``럽E Perlplpcpmpmcpodt source.perlr(?xi: ^\#! .* \bperl\b | # shebang ^\# \s* -\*- [^*]* perl [^*]* -\*- # editorconfig ) break\b(?!::)builtin_functions\b(?x: abs|accept|alarm|atan2|bind|binmode|bless|chdir|chmod| chomp|chop|chown|chr|chroot|close|closedir|connect|cos|crypt| dbmclose|dbmopen|defined|delete|each|endgrent|endhostent| endnetent|endprotoent|endpwent|endservent|eof|eval|evalbytes|exec| exists|exp|fc|fcntl|fileno|flock|fork|formline|getc| getgrent|getgrgid|getgrnam|gethostbyaddr|gethostbyname|gethostent| getlogin|getnetbyaddr|getnetbyname|getnetent|getpeername|getpgrp| getppid|getpriority|getprotobyname|getprotobynumber|getprotoent| getpwent|getpwnam|getpwuid|getservbyname|getservbyport|getservent| getsockname|getsockopt|glob|gmtime|grep|hex|index|int|ioctl|join| keys|kill|lc|lcfirst|length|link|listen|localtime|lock|log| lstat|map|mkdir|msgctl|msgget|msgrcv|msgsnd|oct|open|opendir|ord| pack|pipe|pop|pos|print|printf|prototype|push|quotemeta| rand|read|readdir|readline|readlink|readpipe|recv|ref|rename| reset|reverse|rewinddir|rindex|rmdir|say|scalar|seek|seekdir|select| semctl|semget|semop|send|setgrent|sethostent|setnetent|setpgrp| setpriority|setprotoent|setpwent|setservent|setsockopt|shift|shmctl| shmget|shmread|shmwrite|shutdown|sin|sleep|socket|socketpair|sort| splice|split|sprintf|sqrt|srand|stat|study|substr|symlink|syscall| sysopen|sysread|sysseek|system|syswrite|tell|telldir|tie|tied|time| times|truncate|uc|ucfirst|umask|undef|unlink|unpack|unshift|untie| utime|values|vec|wait|waitpid|wantarray|warn|write ){{break}}builtin_variables5(?x: [_0abF]|ACCUMULATOR|ARG[CV]?|BASETIME|CHILD_ERROR|COMPILING|DEBUGGING| EFFECTIVE_GROUP_ID|EFFECTIVE_USER_ID|EGID|ENV|ERRNO|EUID|EVAL_ERROR| EXCEPTIONS_BEING_CAUGHT|EXECUTABLE_NAME|EXTENDED_OS_ERROR|FORMAT_FORMFEED| FORMAT_LINE_BREAK_CHARACTERS|FORMAT_LINES_LEFT|FORMAT_LINES_PER_PAGE| FORMAT_NAME|FORMAT_PAGE_NUMBER|FORMAT_TOP_NAME|GID|INPLACE_EDIT| INPUT_LINE_NUMBER|INPUT_RECORD_SEPARATOR|INC|ISA|LAST_MATCH_END| LAST_MATCH_START|LAST_PAREN_MATCH|LAST_REGEXP_CODE_RESULT| LAST_SUBMATCH_RESULT|LIST_SEPARATOR|MATCH|NR|OFS|OLD_PERL_VERSION|ORS| OS_ERROR|OSNAME|OUTPUT_AUTOFLUSH|OUTPUT_FIELD_SEPARATOR| OUTPUT_RECORD_SEPARATOR|PERL_VERSION|PERLDB|PID|POSTMATCH|PREMATCH| PROCESS_ID|PROGRAM_NAME|REAL_GROUP_ID|REAL_USER_ID|RS|SIG| SUBSCRIPT_SEPARATOR|SUBSEP|SYSTEM_FD_MAX|UID|WARNING ){{break}} identifier\b[_[:alpha:]]\w*\bno_escape_behind(?] regexp_flags[msixpodualngcer]+\breserved_words|\b(?x: # control keywords default|else|elsif|given|if|unless|when|break|caller|continue|die| do|dump|exit|goto|last|next|redo|return|wait|for|foreach|until|while| # declaration keywords package|require|use|no|sub|format|{{storage_keywords}}| # word operators {{operator_keywords}}| # quoted like functions (are handled like keywords) {{quoted_like_keywords}} )\bstorage_keywordslocal|my|our|state&x=bu('r|'$J"@d;qL0DBmdRQ@X, rMϤ6iڤM۴i6M}3;3;@JH`v޼c޼y XOZUT[LRWϏ ˶ZFmVZM00;}!h0hWYr`U00y0hQP(G+œvjStʪ<őq>͵Ulkzp=[ivz-z[k(QZ 5RmF.)9TEWG:K BbOm/e2H I9RzKKjK`ؐ SBkԨҐ49"iD$}S3!P4}E nߔaӦ} i_!tf,Roe^6`S٭Q \ʔdB8F%+uuYpQ 8/lҨA׺j2Up]`{E.XKiFlu5M5|0z:n aHJްS y ;}{!>!.Ơ,r8"eJvWiHkBzGɑ& v,5p:'Ҋ~HPڞ;})iR0ekP'tR)[;RWd kVΆy]Zm z )i0#\Q--MHI.Z%S-Xuvhszab 1sXa*إkpҷ#qN&l.m R67#bŒ)Íl~ 3=%r*zuNf' fK ȝq6V wC ǹfev>3Qp4׏=AR'IPc~2 )S$R3D*vWՎI!EV"ND^uy;% !k \x"g9t8)9n,@WGs_6)%Nդr 3 7>#(;T9_TNI!SAMiQ%E3Ju&?ϵ) t5 5'E~'<ᇵP;}AVr8 Tގ_ "!3QH/P-"+vJ? "f [ 쐲fdj3RFHD "߰cA'//;dz|n+`X_;}R1)cb5S;):⽑:$FN/ΐ};dv0& 5`MP`*kE@'p! ެD6,1՗ZlVefM 05?H {sTIp;jnEg!d}SC[u+R, aMK*XG~H,C.>8$ťl?QH _'RSc .Px+@hRydFl;&#  CEYQŸ9.!iPLN3*I;AuJIph;T)wK#.qd ޺ ƻ7BGZ [ CU78fBRSZc:%NR8>uSWx~M#>*HNo:>}DS7lEkX8hw7XHx?օ$+{ pYը*5?ΒYѮF/@&Z1\;A"^#@ejPrxc3rtQ)!^jAl>*5"%j[|k""蘕߳0k[)5eF3rs Cr h5lP ֯J֖R1c+~ّTz4 +A{Gf/L_VA4^uM%*P8KFe&߄!_-xwZpȧm}0Hq߀!q~5ayL@b}14ʢ%*!ĢiſrY {ol}*H xU§%~fz8* 2(ev1/ τ>.ٱxܟQaB]t]~.Nګ%:ɏ_ ɑM{ˎLoJe4Cr畻&N_:fʹ?7 !%uL̓wTjE/uw:LQYk8'>Ji;VWSҩbӨz+,֫^P@$4G:&Yq4_]o@`4箲21ɱ4Y8gH0dKS$DQn_xT,w:s)dg~ O?aL*%c$3SΝ >%4$_ˠS2B Z8{8$f! :eD hm}Tq-$ q2*7?!,fl/|"~2NbzqKG\+{_<} 9.^ ^Xekk\Ra|L^ 7AG>괤 رف|7#,e-_'5;vl)LaYoYk=^LwoxnξvF kY#ޝW<+"{1o,Hy4N ^yI[uk\*\ =x~*o`p0-X?'[q5wy:ߑ򿄨:ɑB*^* fcjZq$OUUԺfhz+w=W^]\GsɌ*j]f>6'rkꍢE ^ j~:+ c 2vߺ٤#lG? a9HL>; d0 g[8!E6!Y+,wLd7-%1 Drof"q `Ňύ@B 7GhO0"? oژވ7.Fm^XOJ7CX!Gd]A.U_$Hk4o[=mɭv;`;7D)*|%l[=谉{Q('TDUH^R:5yB"%|T}9!=\jޑ|(lCA?&z6I D_E  3=דR;I."R}r& Oigeݪ;{GasA!_䫒uI 1Wٲk6.<3!qB,??csٹ97;7soʥꪋ):ʡArKWcae,,䗖P2;>Q?{{`) ݆Ҫ5 \H,5U;LNd13ZJ(MK(HJ&dr(TQ&R P\ tԟ a` 6 CY@i$s,s62]kjZCEZ7 ?'6hlHhAD_c+?뫏v&.* WCCh)Sy,M`K*Tg-t&T[Uv0N'$-ڧcV)GbCZBcRi3G=Lhb]}pHjplbR9]< ,JF#URUI(ܹ6 An; ӝed G&_K# CAjNp$5s'Gy!gJA&A9٦cvke>ZF3o`N+̤2֤U)kTvg+dҴ]"1bǔ^F#?9hBU`)X$$9 [◝$s>8|];$* IoIeG~Qd:ur_{+T jwLMS C] }4aYD7fuVLnh6v >S]:faV dH}P|Ҵ[&"j ҆QMUn$D_64,j;m1T6m?*0+٩mU_0q]3꡹.p3" Y׬/D_]V_(I~ N\<·$:m }[k:R ߷ddX_l]V$C"zUVbr]&^ohs٩l]m8B .@ü N sh*[FDŽXlh>Lh-2D :fSiMoMcU Ѓz%45TXӱ:wz[5۠mT*Љ?L,.z56Lv#4uU>P(Jc_p %UU4v %\%-~ʮ mEMCU7·R`M5$kHx[c W G W J JS &F} }b & P _0[1 :@EqV?HP%mXH <4qn HE&dQkආ"eװ RhĢBy$ک jAavtp>(ܭ?KCM4%kw[X-K**u`` }> ~ۊ21UlFݔ'|I1lvb8"%d1{x<)ap7dBCra7V:̆3 IܭXy %8|bB*ߑPގ;7_C`˂ѵFz'_ֆ a!%A P=Ӧtޯ ~!-E*FEDz͸_1*eP(vE'N _Ǩ4J )(bJnb1RCw 0d ?av(KҤ{гOژ.T`QimN)? #~酥.^KbֱvCeylI?lHtтGx ;Kq6(7<}ha'4"i:a CCfGz!:G| 8J#A͑k(Ew.Q{#T ]𼄃$gE}c"Ċ:d%GJh(.Px ׌E$}o_]ch*c}sҵtٝkAmH@%rb'ߛ('[rPPC" DZIY]Nn4'&̓9>a'˲E_j0e3Vʳf 3ϝ3c'ʁ 0TUiZ)[ёsgD sa[QՑBɘh0~ %At yjυ߯kB}wKxH^V1.gy8\;:vv&36VHf@\*-j{$t9=%#S83;g-ְm;zmZ|h*ԫTtùL& հf0I@ `UfN zhJ0ؓn%z-UW$5->>?ߗc`PoxDDL_Av$JoKߓ䛒z ZJӒ$gcj!/X߻4>"Fb4'ӛa,ZH_>/ƨRy/oЬJGYܐ@YtAmv,Lb_}z=wHA)pKRtg`H_}i@#=O/b45Z'uqbYGlbejӳ@K$r - Q"1m]m{nA(/Mr_Kԃ=iYQWzeP$ ?ݖadIJM'4}$WSOs_K-/u? ^@D:ڲӷ΂gg4eN;ã{0t?F&e>3FIy|%ç[>g躡dUyT_LU Qbjz='G%CoD|$-)04ª4‹dc_  ._;p< ȍNW@I$[Kgzq} jk$wX^Oڳqx 9읝hvrs@T8 gz)DSHfuz9qjjg$8KԽPe #2 ~_vnb(]L~2nG=x  ,As$4B{S兹܌9/,/-|"-Zh~U@ xmE0g]_rzþud@9ћR>F0$7cHi÷^zxUUW%T6uehN2r8>8LNƺ$]NգDw/{x'[.-=h8 ̫Lpex&ƅSfl/d(sgw3m'z^\.k+s%3t8}毛P_A?Q57˳ Kyzu:r겙^Y]˯瀢egV 6入kU\gPev_Ͻ:}5^/Laau.yYsWT==Z_v)`^^X &gr L7 bSbCY9Ky̬@9[șs*T;}uM[ *[Z\~ʶ2W7Wf2V.Yv,tP[/ 48`n: \AE مe_[)\%gs4_zaA~\2A eťjY\Z H땥5Kоs_D,RBf3 &̏AyOI{>w8Oew{S7nsO&Y<{bsJO#dUd [ZҲdIluŨ0cO<(ˍ2qH<%c =KšR7Kw0#wy%]S 90B?kݰ]&p`WԜc5ˏۻmSb<T8jG-8G0QAFozG"vQg)mbKʟ8*K7RFk@u>ʧ$̃';6r\D~+!#\8ڙ>39eQ 茤gaYI&1|~UȹK|p>}8n3"b8Dj~җ2SؾLa]!?"YlTvb8Pythonpypy3pywpyipyxpyx.inpxdpxd.inpxipxi.inrpycpy SConstruct Sconstruct sconstruct SConscriptgypgypi Snakefilevpywscriptbazelbzl source.python^#!\s*/.*\bpython(\d(\.\d)?)?\b digits(?:\d+(?:_\d+)*)exponent(?:[eE][-+]?{{digits}}) format_specN(?x: (?:.? [<>=^])? # fill align [ +-]? # sign \#? # alternate form # technically, octal and hexadecimal integers are also supported as 'width', but rarely used \d* # width ,? # thousands separator (?:\.\d+)? # precision [bcdeEfFgGnosxX%]? # type ) identifier(\b[[:alpha:]_]{{identifier_continue}}*\bidentifier_constant6\b(?:[\p{Lu}_][\p{Lu}_\d]*)?[\p{Lu}]{2,}[\p{Lu}_\d]*\bidentifier_continue [[:alnum:]_] illegal_names(?:and|as|assert|break|class|continue|def|del|elif|else|except|finally|for|from|global|if|import|in|is|lambda|not|or|pass|raise|return|try|while|with|yield)path)({{identifier}}[ ]*\.[ ]*)*{{identifier}}simple_expression(?x: \s+ # whitespace | [urfb]*"(?:\\.|[^"])*" # strings | [urfb]*'(?:\\.|[^'])*' # ^ | [\d.ej]+ # numerics | [+*/%@-] | // | and | or # operators | {{path}} # a path )* sql_indicator>\s*(?:SELECT|INSERT|UPDATE|DELETE|CREATE|REPLACE|ALTER|WITH)\b strftime_spec1(?:%(?:[aAwdbBGmyYHIpMSfzZjuUVWcxX%]|-[dmHIMSj]))]/x}{`GyI$ 9˱u+qDdIbpvݭO;K~h+AKi >)}BBZ Z~ٝٙٓV\b7|K|o^9AYN⎙RS-?bVNZ~TZ[ga9+ 0+Ӭ<.w򗙕5-Y/1+ZZ~w:Uձ8-ƨw#ڕbVWi14~^.@Hk;-?{~P]{[ew:v f pFkg$.~ޤtzC/LtIuªSV \o+7dI -9g8I7KKtQvu)9ZFNc؃߁0 E!, ɨ*m5daX0o{r+vug%7`+=];8]*9jc_ & 1[#!CvabdMdM cM t1^uf ~MswICPNA9w 3mT@WKMwb5=*UZm1b i1^[dZ:^*7Hn۩s=@T@`_;} @f@1߫JMGӵ kn`Nޗ[ܢHY_vq9g:c4۶Tf`j۽`q՛YmV=?LL뎇}kLjT;>Rur `q8Jv}m8U6K ($ ɇd, Z=U 2xaP( o) o- z7}0(|Qabdid9]1f($WdlB24BB筹BiðAW 1\.-D@Lb1gÊ9 #gè0"ta 6a00a04aCg0hC=;Bq@!YAYkA@erLaP& 2)EA/ dmA~UQabꢐ5_SqkB2|]QHƗ/ E!W($S+d"D|9 b0'Qзb-}[ &^wb"4~9Jm0s0|7a#aXz9 E??Ӟ]^㆜k' .,خGy9\A~'qjZG'u_B׆0xQ?i,yh4Pw#lWd ~L@C raA-K|Ĵk̙L DJ([:O \C럊4MCg {pbpӲi;-n,'Dl)^icbAփ/ ?/Vǂ{/Tîr%|Q/HIIR.@'l]Akfea2El=.ܖgO8b6P<(WIŴk'3_ @XŢׅq"y\u-CxvY !Oĥe3~ެ))o B҂R ߎ/fU~Xl$Q;Z#QN ;:9"È=a"uߏr;gTRTؘ*Tl |Lq|60DZ^S?-G¤,($GKH LP]- Aaǥy)R<*J"c_ (f\_ "ũ-Tbт#辎-%(68hXɿ\>UjLjaE?-XJ4``py5_>k}Ŋɗ h{ @iYJtϝlb>0DXt' *Fjn|N^B2*>>/me||AY^@b7aU(ĿkkKhk? h/ V # [`*鷙>Pt Oz@صT_c]-gN@d-Z+PF:nZkӪGixL+=|97=m^+aBH3cȐK Zq[}_+?#Na>xY*^I}OhjNEmPi7]i5<~'ih#\VRrÚDAt=Uv-6j+n5H5iE*_RȷpEH}P#Nv0P g.Z7*ifݑQa"lB0O.+)SIJ!: SQ41:{^.Y`tjDpv .8sn4ʚavel͚owqT(e2{ufYt=7vH6>/̏s6['7<5AU\?#y߼]<{𳹽b:=E *ĵNFW H+Q@DI"IpVVLBv⏔dbz@WSL%Ř2kvc4k`E^|BՋ?QN)}p-\ת8ȷL@ ԢX j9v 7!lП^ x$J5Y%dM[ĸQL# }̋weqJd@x/_4&f5Q}zlWܝ$.W~d}eeYn :n ׃wTFO*- Ҵy;ѱL#Θg!0,GΐR,~d`EHh;6~b)&[PuB ~| 5tpI{O44s OE ax+bJ%GLl8\r9#2fi#]Ll.irfnyf&_nL7Wkz1žqqU--TT'{[tN.+傚ZJN/ePI!)޿Vrb$X88ElDu3t3NoturLxmcA5>quAXoCfOfX#X1"|z -uAP, r=(:@y!(ŕ D+0 ٝD9Xmrb,bN<2'H0IU6,۰v"UpKͨBix@%œ-u3ULH)pGVf%S2!-nOSE5hA\ixTnjvc0'&QחXy! lfs;?G/oO;T; Eih_Yg=^Yf/i|[fb];1}8NkGn#8SFtMD /#JYI~ȽҥoA7BI'HJ eؼٸAQ/kmuXdž~rl> t>ߗqIJyC:X&tH-Uyd|Sb@*#W4[R:) y6m`Ip =Ӎ͚%QE9ߢ<5BUdm]Vy%'ib(fQSXHL] p+Srmb<J iz;SfKBu|(#n4zj薙1s|~!1ifwaty_0Ǘq]z4r[ר)բE>p-B=>>nx(nc#{ާv&A$괇I#6V)K_u}#|]jzl *Dz H˶}ZWr4č Cޗ0' }܉S95cGCG;8z!R Ϩ !o;fÙ^uvl8USɮ7I=\64wnYB]$\,؈\xQfiM%;V8 mr@kQ~ sB(Li)[,̧~. t\|>ݢ㓂C̫~f+,/p jXbXn@- n#%/& sEfUT,$[k;=dY,,0dᮋ-O@[`4l`{aϷ)KEWS,Ƥ-Sv<5`G. 8!g1ui zDӘ]'&&.\AdCa=V9X`[M /p&>+JDv/`*>y7d伦]?خ α?V[P K~F׽ k5Xn1EU94^Sfŕ/v\MM,̧~.t:+ŗsb]FBJ0÷X4C.2Q_(a vlai,e&"Jv .[- aᮋ-O@[`M%C^dI)}JF7ΒI&\XoUQ'ߪ( V̀=;Xrr31c%*kQtKe 6%֐uHA'Տyu?G8|*UujT//禛53>9o wwAϠ{eTGl܌=3ͿY͊ Uf+~+ESy7I6LzJJcu8к_Vt$A-˩'ɖdɖrKZYO4O<`.tyݬ%r;ill^CrJ/K$' H" 3waT>6Luy}*G~:,C 5}yg'Sz,HGރ~k;>[:j+ŕBgGph0ր^LL~_Cov1?A+Zp8ԍ==x@ǝ$F:c C'V$DPɞN!H1:e7)Щu c^zo S=ǩBK>ڢ>p'=⾍(5*rY8]@2"aԱǀ~8(&_tz 'Zp٠gë/``PG{h_ 4}"z#͑$i~&7x'ЭP$~a!`""[Gϰ庨z=]"w?lFrleoJ|' o+]|E_% P΂xA,ȏE\~APt$݄e;$o์N~!\c4#%+ ftkq<"X=5(zZ-ׅP`>{n ث Q08 LȰ c$H`b+8 =|E}5+!zrp G} [PIBd9rwP3RiMz;uK0H5411A{[κ*OZ ˒d@ @_~C0ǣl^6 !KE$(jVLnf.[/ sVہo*ƌg*fl}9SպOpclX\)+e}2Uf{˱ T2[@w @.FP!*q{UT?נ@^/T1WIXZ-/jK>j[ý񖠷tUP|[mgn7~޲QvQXs}8}dݼ+o}hᓋwy|g}ݍsYOE/m_IKĦ_ tsE ="钚C4ߎ~Ԯ)rG [dѨ;߄x;/Z,9k]yQ SR`H"ï~}~nޮ>sd1]r;j^[mZ̫N*wU㪤*_WJ*uMSR` e>C1;5m_yVYE2gWmΓlcqRZȟ*R^WX'5٫Ԉ]!FuOvHZEE|x!MrI]`wϻ M=X7[$yq7Ոysxj93J{b=[5l1M#:&X%=T{u :9-" V-IAnO8-wkIYS_m Bj QƔ% i/`k= †P#QƵN!6[i kt Rkri-(ElC]1e/X] M(Ve:ew=)hűwB2ۺ^yb &y~S86ôc G6,]U~jlА[aΚ=恺Al"|M:2ՠ)8 b1b@yϓ.='`x| IH< \6^ (jn8=jдHF\zYE3P u+ٗ kK"'R]{W~X)i柔D .͸bI1-yȹM.:fb1qa/gIt{1 JJ&CJQa͵ {ڕL૪`aH;ýZz^1 .Wk6X%.PbDl?|&M?A @do0m&2B*L|W:Kx׬Jl_ms4ja*LuPw1ڂ{ۣ6^ܻ^o]wޡ{m7F~=\AѶ\ŢmśoQ^imFP+'wuDN EEL15<9g:k, jz"tF.)IcRm; Y 9jP(ޥ5%cd勳sR%6)n%xITkb̬Bre"䌃B(r4kS '' +xr[XȫԼ>3cT=f3sAhqR~>,NS<1{=#ZK4?]j>z'JZO1!6;1P =*ҙ89T$LK$Vĥ"xfU#{OqRrG e 33ѴP3JdYg" p*xW.L+V7~kGoJcB%h2ާ4X%,$TdGZN DEtm ԁw` Fg$8_X&*EH]m'ln71'U-1p7O3a\hלX9(W/5wcv-|=x.8tp ;0fl%c‚:[:r[5"r-Jp[XA#$t^0BXF$K1uX_%ҟsg#,N/DggV{}owa偓znW *dG^q!H?`kU5; ut% B$˛ek;<3֨(v s؊ؤ\PIWWq/zo58\ ת~yr<*'¨'ji<8mZ3 aE&`p+ j5F6 gG$1E~,ٗm>iVaZS4 & 2KKā-^6Rұ?ђe5?f,:T{8t Q9O)oMZ85[_7\c:՘fI7Hk Dj,ggUzS#s Kul 2/Qܳdt;6"?aҘwS̍x`촜yL([1#s5Y[ ' TP  dvRޑ`Ba~J{vrsΎL4=tg=0K85SקPt<{h?#T?ޖ Pm lJvKRIgKnKF} ybT.]+4[_u5^ŵSgJ8~Js PY{$U29)ieX#Mun pVPǛ'sGtsJFdȻ[`=x.R]R1\A3hwDwWKOf̢G'x"Pf\>SHҢד~ryUCWNz t?.mX`ɹcsBG99wzGN͡%@ǰn1zrb6"b&6r RiEO;7E .](%?tJ!Rg L=:Dn6y"ExHm@%(ۃu($66=щE I5QplOJ (ېqh\mB/i-+tb+ؠxloNB Wĥf񾻤7-7߫7"~%:^C"`!!B -Eⴢݷh7ݥw*%?|WIw+/(a3K?E+t;lI- WE!1<AA:1\C%ʪ(!݈" vx&E 49AV%oI2SN ޲2BYB>'ǓTY[^]v{Nص[8~aM+0BBTc*V|<%dj{ AVT\|@iWqڍ1k;'7~LbcrdLB7ƚnR7HU\84>h_LQJҪ\I5pa^B/)՝;uSt EUERR\:wjSO,[҃hjU"]©k 653'͢]g@?U+Regular Expressions (Python)source.regexp.pythonxW[o0nו$$.hqK 6mI< -N'7h]tqqV,-'Ss{2YR)G~sG~^%8SYgS,uKYYLT%z*f_hO({niKp/5߁dTdQpY! ']r>(552Rȓ؛p0 ;8A #m,H cWt<1N_< М?i֫ Gce(>I({)j>O R- snK^z)ˋDv9 !,f0J#wpOd`R=zl}[pxEjZѬ˔s|$!{ŋ4ߴga)z- $e B-dN$_67u7d-G V-ԇ Y>9z dḶVHgUZXnWiDYziAo߫8\a e,*=9'"U<QqC@ }k@8z8P6RRrRprofilesource.rexponent(?:[eE][-+]?\d+)var$(?:[a-zA-Z._][a-zA-Z0-9._]*|`[^`]+`)9x}{wq~;؎w-f97XޥHi10HDy`Xb ݯq?֯g(yRtUGuuuuw'75iRV8N[݈x&d]ݼ>mY/\xj0_GQٲvyUޙ&Eq'ic_s?\XMIߍ*$MسAZ|Z|7Muq9J\횼kL~cP"m0դ}c'xe^ty+"ݠUDZ(X{ЊSJK5\t }cUt뻃dIAuPgMR9K<AYOIϐ,:lꫪ)/]ոqʨ zaK&NwYiOnZa߿qk^ڪXG7QKnMe:itGy\XXd]w8>lډ/ظ@֍7[j" K 9h8:aB$JDEW?[ r򧨡 +ARYeY. 2keL98 [5LKwڟ^Sr^hŷuI67״o W5 ^ʻq=ĤpmK=ۯR.7_TYuhaԴ|v7;o^_m..KmlV5{6-p!5EvY=R׍i6\L7QKg.uer栧ղjyoxߪ"گ{v'1RAs+mUo6_~R ^WUѳ57P7Y^bpgS>W,v_R_տϖ~r;L,OO&mL+I>0iOvH Pdd\>)+h3;]&!zHޛ,.$eYT%ƛ Iۆ%YEIΪf,T$|I񈦹5ɧ!'Q&9DAc A%~˺ȧTi$Cb@StKL^$$.@EkzdZ@ bd5LfM)}-"Eia8Xl@RMjڭx "ʹfBu]0e۽vH.=׼Uj;Րf]~5jj^vj3 h՜ضTbͽb :Jrm;&Kdni|X $J&>\60h{&Q`mɄ a= qbvx$_ Vd٦E{uȅ \EK M& kc*>a=}@0ߨAv % RZ R2(AV&ai䧱4['y"=/awH(AffhaWe8G0Gŕ+rTDb+,b8&EDy =bRIzP-,%_HkCmD*.0Uxr+djA`5|!-8J<ɒkUEzVYFPQytBr!+4w P%$'ݡ )]vѥuη˔Uᓆq>=x'TO*'ҩH NNZȽ;NQ(vrn!UPjZ6,S &غjߜ6 OyҔXPYeg:e<Í6UEJ=ZkiFynâ;A4ׇ!tXp+UwVB^\|mT-i%e{|Si!w =,ecR¤# I+m*{hDU<AD'`\ \,{՛΅ `W6Z@u#bdL=A:Sҝh`t~Tm[7UPrV% 8rAASud苔زk #(4 {# є;_iN:uXS@g!K&J6+,"~dO ,E!_?G-wjYV0 O⦟镹gz♮uW!i({&BLe =Ğ}&,'r-EVNʄ>e7@8 IfT,(B-/0 kcf%؋h<ڜ7y dv4Y֑69=˩rp9R!2%D"U}ip /AX&HDAdNlEu9W:70ᨗ N!y44<9ϨU#m9"S~VTĿ\#?ýr/,= ; djmHFb+;āiRՊ9Y˽8Ұݴkõl 5W"-r7*ώ錈Vrϧ\9J-C[=-e0mBqw?2F}kF2`fՒ4]{#4*Cd=ȸ1f4 q㢦dGIdUupjJIp."paop(!qH114Hԓj5}.˩$+YnY=rŎ+yS-kySTֽ>'L<˧ȴAQ< ዒZ9!I R&*aJmB;xaXVC^.Q6T2c{>mrgd4ЙG<<soN)cILr?3.Iuܜ W]05;7  ]"F @ Sɤ܌͈֕&9$8qUs|(6tIR' :?!$;-Q}kKoU,e? fhҾH&Py%ɋKԄvg4@g& ՙHIӻDvki m̢LJ[)4'/gq<qAÀoA0˓Z&# q 'H@PZ㺅ˤ7PT`CzErق2?dU^dz< :F/B d09J M"!0W=DUcAL;ML@k|,iAM 0x]`!x:2$8l`f"$zgZP0_m˕L3{V4$&ʢ_ZMg2Vh<.%WKœ6bH{uU!JUx~8#ev΃<<֗/VȞbRɭ|ކRd'E[!U%ϛ%u,M&'u<3 #wS~ex{&ڈƈhUzAJ0aQOg|6رR*kSnĔR@1%U'.DAlX,v W>&ȇ]>#(ZMU(\qΞVӨaܕ(EF$N\s`-mw[R%F+&LzSL&(Wf6}U4:JU3d. c]*&s\%` Q_i$2s -ܝ6֪\b0.w㦋8[V؂LܙtCMzv|Xjdmh*=N0%VWGgRSbs%8+UR.d蓞iLQ0$[v= .\#,'taM4@TH@r @7R\YhIȴMw3HP%V2ݑ: TX8<Yٴr]qtw!CאYت&A nIE-vc!v3&ZH GQ6.cvݬ6Pl$>˦@[0;/eZ=q" ~tz0jjoe(V@$8<.$,/ Rqe|!C`QKէGhٷEc(}%QqRTKPÐKg Kfi)g%"-#A/P(뵭Zڡي#1B@ۇn Z /~8|DBdxWHZ喘cq^naHO-SJVF)c爘kP˂8Ğ/ͦ6`vᾥwrO)Һzu#)C[vd}$%Y0yHulBiVhʊцW*y;` @K5]s@) :Ѡ!~CUb$uJe'1iY:x[/x.ih|۹(*G"Cn4 qbbxʑCf Ei5OjtLش&z \sC'CjvJfr`f&̡u .[8!STdzle##HZ U@Nƌ06*:A:BzcP ô޾ԗuY@؈!rAje\< Fl[Ad?v6"4d QR&0Xw~Kb:BDGSJXHa9òPǨ#|t1K?@׋ rN5bB+kgN66Rȏ* F~bW*o+c9cL@$:qi2tq(ތA:v: l9ð ˎ5\C!et/):B e+g h|l͘˲z`w4heV3&n{ZtSC6V?j ><|/1 #2-UJGR$M.=mQmNc|E^8^1 MtA @C Di$U nt)_e &O !5]PmϛEBAkKVj\ 9P6Orl Y|-e >U+[5G:fٜ)8SUTؘYM"om\inA1mH/IO_aGOnJq64~tZGjGq}#玄tcaR%/&7"$E _`%2 6A©sXO<=9F:%jz$n.ɈpeJŦ2YJ"ě3Z)lT󆗠 - ZD#Kʡ,6Ʌ<,B4{m 8ȝ̽YA[Fr[5x*h&j+xSgSŜ Q_WrB Ֆ>!X |0-05J5JPq5U^ϑ! yy1\ٷ%CkR#jR䲭E rJKá5Q <}ki4*Me9c}Kb."Dx=e-/ĘEEN K DQ9I+^2v)۠͝!/wCJ8^a dTc=Jk2NKd1L,?.˖btFwd8ydXSt؅ɍwxxC#KΒm7 [\>rZ}UB6T-Ҝ:Xr*$do&8BP >>JfbRﶙP R6#p=y?|$ {{m]!1&,Jd}]:$<-pBѭpq9oKpc|I xZYҹ-Ow8yJYxR:^ /$WDg|P.ţ͇lU9lD< /<۰ VH9Z<Ҳl'x6\)t[0QڔvUrQ+( qD2T`H+"l,`p K4 hYFO-^D2]&<!+|HsH!6>*`#s taq7cXua_o"?_&Y5E lGtqhNC쯂> 6лήdv~DuL(S#@uS  h%EB60%8 9UB"\>ş6UWr"U ZꢙSOjW@ dS9B,068ygE,ČҴI!XJ4,A\ 9ʴN"%oIs KH VCEMF_Qipu89N${Bu|g inxK6Zt t3c]sbg  tC{SϫxR+J @[nsZ+ 4kHPHU6 I e%jb=R0ev80+\߳UlӢu㤅{ | i;]e%753S9Iˉ$ϧ 1ױK2@Vm Qq9:⶙ntyd=Dܑ}2ӫR!}OSbwҐEfG9͏yGl?+Fws#Z,R{`./Pv]zAGnG38ݪr\_I)kaE8 Ê!2%{lC 12AT VƹT9 [%t]VtxC NS2\|$30[t;V7^`ߠfaviF?b"3>~}ِoD nɋVe`Ҫ o5ruO|?'r"L'UuDm:]N'X"L;{NwsTGfb9{Lm_`+`lGvi \6sk0d]xt:EaͬYhGw}5A"O/N9, r  kj  m<@ϧv6ƪGqcüg'2'kE%ܶNpP Խl"r:&InB΍{evs6({*<ܞkݬfsR;6^˻.gDy! 5Yph]JY¬LxVYa79~Ѳ)($n|/cbqq$= '9wD  'lkZY%r;Po rE>"VIB ڒrA8Gx:#.%I"Zx+ɒ} 1޺-VVD*Т\yoi3j2mSvգ+3W<)fƫMd锵α[ϯsYmbm? `Hڝ3K~썊%ѴpIi9t8 i |Rf#ZMZ Q&鴴F9k1j>a/RP%  ',5u֊hXkOa.O8,·;Kg@PnlsuF}V]auXVjh1T4]O.rVh~8d(y?H1O`uYΎz8 UCBXҦX q-X5x=2]z ޣV~b2Th炽nkbc~:xhqbCAc̀k\Ӵrg[֒8oYR$BAS>24YNi퉹ihȊHsZWH F :+ _4 4\D/ 0Y"¨FAy,BVZb |NRwDNe&ʁA*mMw2ghKoG쓈"a' >;β;-,fe³MnRZnU.L#\7 *BK="7,`͖D1% Rd3S!|VTCɎCcK:_G]'H|a>0yPfS>Y Zfsj.3 "Zm'cuGvXc͒EN}MygTamǙ=&AWGq =YAC>Ma3lE$Z"<+C̿:Z):4pz:P8';0tHOCB 0JB\p3kϥW4yFӈҪ$B2sE{_%K'l|k叅>"쉥5W id}UE?bXۚ^,f0ohTEx/~_>DBN]9HZ4@cؾ5vҰ߆ z]jtG t"HďKĉn*05:uXs &)LgGu)x@ 4{x\_?<aڀCpR*Wfٞ#̿:@tߡw(GbӴ"Kw+hFp\[9GZj*f | s&[tȴBӫ 3,SLhmKN"gc8W}Y@nX1~`澀ȐN=~DxtxX$(8L-d2]x@)ӒLQ6ВlnA<B ػp\S^8u#@ۀV7&!6LDj6^+Ŝ4^^rf3NPؑڮ<4N *> s$7 2_EYp{$t|be$,>"͒p@tQZI m{SSLT:'[:q\q!t9_)=G _q W(lIaC1,cd!x03.iī08[\6$VܟYJª ~@'A%-Ŝ@I-Wlv\Z=OL=aC_?Hg(o3hF٠)Mߌ/^ZZ^$T 2BUo`3}CqiR&)%$AKrп)}-U hY@mˇ8T6D LBq b'x_aaTh4>]k\4e8^5 ێF|J爛v*,,C1thm Ursgl F`0CXesXxGœa\nBL 3}竱df[ZYDۀPRfԥ KcxqMq@յx&bOFe˵60L':>hku}ÀS 7eͮ%(n@bi;\dw[g\֋ ;xgԅ`bpQPU)D#/.cԜD4f@6//,YERC5<Rq2(iH|' Z|'x}AH9:{+8(@Ns4i< xވ}xGlXN1uh7br9~DW;E `}j ^59\4Oo1$! w`b@NV'iF19]#_) + w$B|}WPI^MA|@ӹQÝظHy_Nxq `x |>\WŶ6=TKϦe8ϥ񡐱|{jGD d_ib9inXE tQ1oUbBZ7Y>isz$FݝgL.!ͮ|ᨭj_*L/uT\X%&E9|KEj.3a Ur. _sC҂jW5_Oo~!ష[/c iVwmH#Khtn 6$ 9dw"55|֚ `۹9 _&hhVrq+.L֦Nm~Y{k۟Iݸce"#P@xFs!=X mzQPQeaoƛ# dah]`7qj>)~\(Ϗ koׂQy;l%.3bcڥx ۠U:\'*2G79>S q;On͏{Ge)hvN/<3U;i6 h#/f^귘&vGQ gyQnS(nSDEbRV̻8NfG?}3Rw/=׍gtkG./5$#n/06OҰFAmV P3|5O:K^>,ZK׵WC?p:1#M>8Н$'QA)eW"уm?o7ӀV0_،㞤J8yd\kISLGo7{Arg<) i0%BA=i+dĆd&{ KZ󒽇lgܸ?TGGф@=Jؠϲϟy9on^ye⚊ъj3L7ںyǾU?{}"4?]eL6~xyM|F+8̌Yݠo~3\xnۛ|4á`odPz5-40ȷHx;[^s_kkRs*`6V&/I/C7FWU~GIursc6>bn+֝%~o~T?m0gE|bQW =T^uC۷VU:q*Cq͕)_~73qW_f_zu_ t4@%>~+>"U.EZ\ȮD%'ľS M&S8Kuyro\J}+U뒔g$̭KzS"G)Kl+ץGDK""\?%eiyΕXsJ`\ӧ ˿_\`~mUnC+ҋ<$5o{ ;kk~k k5wпcFHFٳ7xKzx_eJEܝί > ݞd9 K8z>}fNWuYY7o؃\WwywXPJԭ8lWW~qgm6@|j}^{#VtCIQs3T7w?ks>k͸|T!?SEE;h߆Ee#Nf9+)& ~wE\7^ZuU_޸FHEYwUZH,,'QLV z+]eү79ƪ1^.Kon.ucK}g/U5Rd (R Documentation)rdtext.tex.latex.rdIxXN0N m `LfhRh?watj$a$O%Hmw)7vUvϷqB*{jV>pBn("*[ >w:r}&tB(Ïdcvp!+Y+m!\Z1|bOr* _*3]|t( `4 43dDp6{Lj0Px0CTt2L=kdǀ ԾʉC>cOJK)["M<.-thHJTGjG4^bvX,MmY>Sֹ Tn E@g_fLNyƸ@^pJC -m0]yC Z֍g'pۀBȹ%!0b%M!6W`UGeYFcR_F˺*S8(|n( ߫ޙ.ꝅ. O%_]w0SJ۪u`Nmf6*2j:㹤_5c[b EX@U&@5dUt!Iij$ZQ}cC @eGe /{/Q}9,wf~\sI9S{gPSq04LӦ.粢/w^QO %zv/6}vP(C^Vk~LХE r^~KWu&:O\O HTML (Rails)railsrhtmlerbhtml.erbtext.html.ruby>xڕRN0x`JH@CBbWCTmbJInJSˁ9388ReaiQ<:Ӱpn9wY)(Le!ۊ H_]¶X? _{Pώm.+eT~M]&0^>G s{IH꜔%)1Y!H)J)>zf# ](lDyFv,]ԕ%~N^ !(}W̺_-I5iz{^Å{}_O̾!K)ȗ69mE\gZl JavaScript (Rails)js.erbsource.js.rails*xڕQN0/4NJ@.+0Ȑ8Ȏ JۅdHX,>\:kr{6[ဇcvx]n:LV[3m;Qm&)ؠ壈nE瀶u¸C\ՖN SQ_ҹ>N0Win)/Rh$Vh/s:btٹS|VZ'g}Nj#ZV5aӬ1w{/v1{J8N~ItSm 6+K㪒wg3b'.j Ruby Hamlhamlsass text.hamlxڵVn0N1`RIz ތ'41[XT1$&i _Dn[e >;Us,tfBլA贛u> 5.w5t>FUp9sjޕSrjl AI>ib4ty:] o)ӟxK ^LJ3.sZ؏ ~҈@mPndNPf5r1"joo`aܯL_+B/uš%ĺV+nཧ.(,Tr%4(0DT\S}0ksHGgt|7d /,!"Լpyn4EM)`]_JUјF$z"s篧,NUgfO*omk٪k GbOƇ^ňWA;j 2,fKs/"ۦJB---'Q3dpGs܀Y>V.ho^jWh[qg Ruby on Railsrxmlbuildersource.ruby.railsxڽWo6w>um t0)Au3:]1`>{%QPTH*7g{GI"-mh"$y#wϧ3Ŋ<" R&I/෺2n ~ug׻7?n NI2~,ܶcE$j~{r7;Ίټ:s,`ƒ8c1EH58Wڶy˸jDX䏃vɸCΔqvvWmV*ܫn~d28Ow3[ޫ<N1p@=&dZ}-(n ӕКKՔ<firwg"ȂywiK:-Oq HA^PEDŽ:I&=Aܪ^bQYMba}EdIx]49֓pVvl"IRLeaE!V,QSjLe >+u8nB ! 6Ȍ+= xS(Z_^8ݻmGg}-xѸE. ߿_ϋ 4<>|j'/4}H5ޓV_`3um6 妲pV8ܮ!u "_X0eI9魲<8|Z$b.-l~xuk5?+EimnnHtm;."7k&ԞAoU_)Rr㖧?-%v SQL (Rails)erbsqlsql.erbsource.sql.rubyxڍQ 0J "-=@U]M[lssгo0ޤ KK8ubKM*%`:4u X֭)mv'"-+I(hQЂ[4XSEz/f(~zQRy JN oDs I>ERcGVy,#߁o6xa3zZ5Regular Expressionre source.regexp char_class<\\(?:[wWsSdDhHvVXR]|[pP](?:\{[a-zA-Z_]+\}|(L&|[A-Z][a-z]?))) char_escape\\.character_quantifier[?*+]invalid_char_escape\\[xcCM]known_char_escape/\\(?:[tnrfae]|[0-7]{3}|x\{\h{1,7}\}|x\h\h|c\d+)lazy_or_possessive[?+]?ranged_quantifier\{\d+(,\d*)?\} xZSF( Idܤ$SPBM'¬eak%WyWVZv-_дE;~.M20L:x_t,ٔ{Dک\d(*,`>P2ZJE0^vk*@п^;kZvAxmHG ̲l#0ˀ?VhlЀkc`z스xNC\!QL6F qIU:a&2 u$HKPfoE%B8dfjPCHWwm6TЋ;ط#'`jfv*)B iBX\x  7K ]‘nx@g hrCU*K3 ir[,ZUG<@ W-r NȐp^SRZe`Tt$]TJe P-۪~"vP4ˮ4ɲ*dV*Oth]j%hXVY6UTꍽYCYRSgp9S𿼖˭sVfYOv]8gOW CY(ےP̤mA\nNt i)cӸ}C9%Õ{q?迅sT}Lm,0D?ġkQak${XlW!h:Mu">d&&Z=$ }mx(+[{,yU)fUK@ma>l|0Ԓ"NVU49 Mz6S)i#$ipW 8M,\Pu(Do/<Հ3)-ZFD%kc6h;dq?1/;k>0<3Ф2 i̇ uc^>*prki.yC̤-9OK|ΜsQ8|ٺ>Iz'7ChEސa39Bb`V@y*r 4m-P@A[ଏǶ Sn Tk 7CFz>1Il$ct#% m_^m~yvrs Y985¹H1FHG>nj9K9Mxe#r`J)`yz6F c; 푵`?aPs"pMӁ90f's .2 qJ}B "mqLXri1SI Ufj0W$ܱn#$6&,xkL6^̐ì^7IӸB|=F4O׹$dҘ&AܹEJ"LiUe9] o$3>~ +htq ùhRq5+/`m]k[QS,O1ݪD;Ѫ#=qN993*5#EW,×c;逝 8`w(^l} u;StaNy=棃Gm:\>bPs?Hh!M`c3Ix>s]#lO[\^8W8~/Orgk52nO+u~h\jb]V>y球] o].%DR=*U/zGERTY$JB5= [%~z,`ƗTlm`|9!fdyD6kVDP2$M!\=*CĄ)Q|Jv/] "@vz1lTQUoUlOOA?}2n|U[_`#0[>݂잶ӏGLX4F=]))?odigits(?:[0-7]+(?:_[0-7]+)*)path_lookahead+(::)?({{identifier}}(\.|::))*{{identifier}}!x=z6N̤toqHv]e%ҤJI͆&Lҙy}{_>żH@BYI|1Ag9;B{=T2[m\T[]\vZs /~Ï |PkB) y/Z_Lx8ߝyg:1'IĀw `WuՊu\컓a {AVSmc⸶nRl}20S) {{S?N4'38~Wfqae9u+5i]lv:X=!)#L$ڳ ҺVb[!2&$0HãBICȨB? K}\X,,x_rD'? K4dXdXɰD&~N%>aq`A fu+mք5tTgIEJHT|K\1@ 톌FI&Ia6eє 땔 ~WNrz3ueuVj`l9'l VT\׶e͗0ۧRSBĀMaEe,WQ$?Jaʠ)^SBjt}kB@[[6+N.%s P##Fb,1Փ`@O yumf4djj۠=#4 0*MkìOx =qZWu=W߱|P]s/wYڰ., K<>a _>z=q#Ꮿp:P+Ā | =0& CV2z&QWAf0̛\10Mq 0o'h=4*;^>-^3ǝXC(99@% /.(؀if,&zO!ĉZs L = `E>zMɑ!Q!󉄴g(8D Y@K@CcqĬHA JAq։ !%D/UsZ*A"9cY cJJ ’#'1?QHOˡ% ᬁ9JKQ՗(D*AW% I!ݐT)H$LWEp]&+qJE6zp\ @O!H1-Iz>J`%CX8FQd$ ~șa"hVƣH'z"IN"'Y*%ɿ"z|=89݄ z>DmU֠~K8ZΕDd~=VV7y[y?U|6W0D Õ ;߿ws߁a{eycĂ?Y##G\QRc 1,?JC)WX4 ^$ s9V M KS*WR"`Fx6 Q1?pyQ GΕO\i^ :ښetR3kBSb#\vTu(>ⲋ8p` BǨV}] c}5ת2p6U.U00oT ?9 tFXzqTe 4Ồw$CiBK zIi7ۭf.]R)AdC=]Y3snU-w !Sӂꆆ^Ga|'@] _D]T0 FR3$H~C6&ObQrGe~%EwۖQu+} CqCI⠨>XQGs; j0$3]ߖs=!\zDPʹiiOH:%` b0V2]>wmL_/;U؍Y屦mU,8whah aD=íRWtXLľfCX\7R]_w_z^9ƻ.R]{tl0nWc`'V(遵Sxba(]Џx b鼍>? l>²I+wjdc,څ9/%I[fևt۝&) bc5Rf0Pc/yjF>N9oo6E k#xu}tBnYL%z[4.[sǔ947ev/sK͈ı8r/G$fh&Hyc;7qpXߛ-1d1=\ȗd</*{/:zA⛳N)xL wGBJ$&%6]J9j[^fQRt p+X9|7G3h`xi^,p3E67VQօQ>)WU~!;$xzьrIK#ZV[[-yElL~vٌ27Fey) YiMpC71TsxjZ h G05*.QI};9(RSSHGN(BLY煅dRO/@Y lNfԎǗc N0(Kٖ}6VV B1Ƚ1;K*J|Gmu+grcWc9K-?Ja 9#=L)",Bw~YjKÔگ3N.2saGQX\Xa2V.&-Z\Q)N̚hM.QӁA_D91XwT¡ʍ`'4]̫mdvZ0hv 2Mm7Њ\_J@?8`o<u27icl%xրHPټԙMrZ!%ILVK)/dV}ݵBqږAKnDB Pgޤ>s~jkXk*H[yؔ?:Ji70_D2ʫdTP;HFnq-̩xf@iz ;}2^Km=xå?oLLԖ; WNbtbCt3xKFLpG:z3>5e L$o=C?r$3Nu<:We Bir!M/k5oE5 $de4\&%0VtsO{/|Թ/[-ϟϗR884L6 4®lj;U`,+ι:iGEu N%%o>a`aҬC4*5}!du\>%Z>xte9,v𸝥31FS\S,Tq;mA(z7$(vO5utBHƂ(X%"85…: >eO9,#]C*-$:s@>]7 gd4HE1>jS&>DV)o 0y0.˾X<X_^W/{#Éؑ]X(UZNH ʪ S'rD_•cbi??hj>+M':dMpdоs͌҇Hd,PK}_K~8h?%~µ @VeA"X |W c, ÖJ JTדy?̓nNAHK:mZ>jŨօPPA7pFcAu:l C*2+ gMeLiHZ߅S(s80uί!rB׏Py86(*0nW(1ha&)GXIdoRYMd]*%6 ߍV !F1$oCzZ-"1gR[PZI$&[٘# 3w4~98&'Wx[v0?%Ҟ00b_I?f\ariniaJ Ŀ3 I~t;Kbt%OϏs&x1Ooa#O?kΚf anO>'Q?5i[ѷ̒L)N,6iDv?p-b'vbtR|seV7gϒXDc޴ķh]ޓw%-I) oJRBυxM>g&.:0] C]Q+^KЪf@%ma-5g̗c3 1v\ v~cDT [b7s}T!TEY 1ƺ*CȴFh羝:yDr:tfOgI!S_-0رfuҥ jL d05iE); r2a4]`z/蝾x,w«Ag8%FXkUe$ٷfk"rbzȨf ;}YN qOx,7ŗ|{|]+дNqjCXHʆ7ך򷊩X|k!a KMXjOA,]28- B12!;הO "UlU"GnWJd \˲K"(t9 ޔHPe Iʔi ita)U U><< Sr;A\Ǧb] TV6#5'd@mos녘*Qxښ^}83){&0sje*)i&\>=s,XcB-%Ҡx/: j夻pY(mUցgU{bk=P( p"|:TW: |{%5K V0F/b1[oppuSri8Zv\բ'+M01y}nnϝn6gح-6R_|Ú9~z['2chE/CpgϱiJIq<̚ 2T/n8!C:e_"rAElZ=q Ezd&*YSjL)2 YP/&SC]u*u(MAB_W:uELpۈyituΩWNY}I=9,̫];~{M= aըeg`YF7:ωS-I$_*%ㅱtK0.j/d\W ADXSrNl˲?.M$!;^FUE}w;e;.$ EZi;xʬܣw);)U8(򔺷*9{F?g\Jce`ۏ.VYi$>1 ;E&1PY߆{$Pyw5.dsnOLJ>1ppQ*H-Іp{Gu;kE͒w"BVTl pymm3eρ{w,2 JȪo%_&!PŇ#o3Y5xon[8 hb@* /I(uhWPD?ץnˇӢ#umA ; 0dXCc &QZDa7B$pZ*_lQiMlkN~\+Zh*< ;~{Qsy~\(ZP2 Hi642FiHULu@mٗIh,2{FVJZ;InMdLLbltnBjhjWo o藰l > /i(+&R:!5a5!/`٬M6C^{26T)cwA^H+W3qFsFn #;?rx gb򑖪=؛hh۷5RCfމrB*W.;tiW̡ZAW_At&hH oZD.Ib7˃ؠ GeX4AXk`e [ӒN , *q+7[7갟 R&F5k\B6.Cargo Build Resultssource.build_resultsxڅ `Ck#qPچV4$&@ޟBğ|${/0-JP.]Ն*l`9iY#Fz0X=W5گ._ѯq ֲ!KDdOP"Mv'&Ϥ% ^x˞ҒLN1rδW_t~Ir"|i X`8Rustrs source.rust escaped_byte\\(x\h{2}|n|r|t|0|"|'|\\) escaped_char&\\(x\h{2}|n|r|t|0|"|'|\\|u\{\h{1,6}\}) identifier1(?:(?:[[:alpha:]][_[:alnum:]]*|_[_[:alnum:]]+)\b) int_suffixes3i8|i16|i32|i64|i128|isize|u8|u16|u32|u64|u128|usize support_type\b(Copy|Send|Sized|Sync|Drop|Fn|FnMut|FnOnce|Box|ToOwned|Clone|PartialEq|PartialOrd|Eq|Ord|AsRef|AsMut|Into|From|Default|Iterator|Extend|IntoIterator|DoubleEndedIterator|ExactSizeIterator|Option|Some|None|Result|Ok|Err|SliceConcatExt|String|ToString|Vec)\bqx= {4NB -p|S(MҬ_^d\8֛wכimɒ,ْw OGhf43vzdcGQvƑSڞs^GL5 9e ;ݞGnuB9!/>wS&ȜJo#U|:`t0y!xh:N;`ϤBء'buV!%?z*["NJ98~Twuºd: ߑ&^IѾc&"k  ~?vT̽)=)c ;!Z9#8 5.UtV1BwxQ%w0Yg A={} ԃ)(#A/A@B9 9'Hfe_6tqף!]?V DpJ$b$m YyC.A~! oa^,B3 ݨlgtCtURg_Ĵ@4z\+%\z j!*7Vn*!1Sa-ΛA|#vZz0tB; B ub\< gm6 YŜwRŊ\I10/a<|fzz )Wyfgis%{_M/-nv݈ ;!! ޱKN /\+JA!~W&Ψk^f ͗;C1__^ՓpB!s~&bG1,KB9Gv1O\' n\va?H;tVzsq;6'{N6vDMeT ưIjoAq@rLqe$pŮa9acH iȉvb/@6p}YSwzǞAl#{oqq:TN&1 ԕu 8P=A l0s1/ "r1Ȉ$s81Dq*`Es̩qm}ݣ XCp3b_O;* c8i{ɧcĠ5e4EJ.vx33)vG8~T/<7+_iOj5~}nwϷG%lZӬY52+@]8&.$]Lz\,Ac[\S%Æ[ւLN]b7C| o8]ifhllkMoAׇfj,hXZ0^_UJ_ yz:HQe?*Pm,yB/e!rdm4n$rP[3cKa>W>gT CVG_WzšYNtf _z1-jzSu}.Z3D\x\mV[sB֡OD^VUha\6^["CM,qC!VExGd4\Pl5$_O2׮Tp2̦B {eVfNv|H(5sJ/y*X>9\8@s7A7tsTpv7gMa(l.d?!3HLfp,TgQ& -j/>rI]58lm/-hFق?LYJ ukbV:5͉41 d>%ė`7ᖆyE7HCbP׺?ۭGKm~yN#@=8l:r TH;]})YXv\It !?.FjՃ.i0#yu[v3yA&;'1fqg A-N~p{BN'p,Aa!S$k&;f`2*m=ɯJ+MGu(2Z`UKXt)j-e@M鲲: bʫc&Z!eS.'hP(sg ҿCd}mjocĜ^淆Nw(=MpwuVhuxv~@7DϢ$x$:=s.W\h7f-j]k۞cRS|kbq)Fu5yo]n}aF,t6V\ 'g\P\$C:{n*/MfR@6ʄfҒJ9io4۩-07Q)Oha7:5.gCD͢P`;uAzj8d?CEk@aUYb'PJK֯lYuyc}gyf\YZgZh pN̄TW&&[8ս G.C]|<?}uƜFz >Ῠ(_@ 9}{'Qxyv/l{N&aH<"ʀ`o6?8#TG~-:wkG ''+A &suɀG|YixEBV6O⌗ Gp%Smu9yQi$d[lǐWaqgې\j(^]\[zu˵RsF=Gh8Z.GQx)QǷ5CATMso1V }L$c˜ -TbD{@e8m^\޺ _FЛ` ߌ1|3Fo TK>sk%$puDO|}uꓖVu,ܞĞR:?=CvzyA7n:Oג]kq};< %呛Mj-*4{4dm/n4Rd ;],,2)QfKJ*ܤ+ۢ HIă7_*gъ?i`cbSN MluCdײ ;c?/ <]t+or 0rhXLaWN#<1Cd` Ξ׿E:t&!WA͢>Mzs2NۺH>1= [t<WMT+U䘄voX$&Hzܹ6T1ȝ-  = Fr3`@7ԌK,@ri B0 {Ǡ?hc_|)=ZGh*Q?ř qlUw!`g d_\?C(T̒m~ ȃfCI<&314[51|vSQLsqlddldml source.sqlend_identifier(?=[ \t]*(?:[^\w'"`. \t]|$)) xZ{sSi\GdQᱭ:&Ռ${D)iǰ#p.qAQ5G?L?K883so;z-}oF>vq$o]C75%:;4.JK$ゑSPۅd 7dZ\DG}`0,&HRvIoI`36-:\t5Zd(~Q&",!>emZ\B2DФ cqk͋ʏ2n N:Ob>o1o?jE~E~&꽥>=?-U75ef 9N"_$IDQ ̜)VQ# f䪹=\rGf"Վ2:^.VpC>^# Mey]0J}Fu{Pa8\xvgc)8A-J8Z?0'FQjIg$,,T,|厰`VsX@9MlvoP/Z]I\@ ޚ2,ƢUoi2r3;w@ Aph'aj>wEūŗ\ZpuWP/{пx,.4Խtֺ(&}(bqֈPxNO3cϾ>=LR<5-HӊA9 ҩ(\OMsT!Rr N;絜Vhep"k:hDB~?ƌ2@2ߖja4ID^'8k$ QIPKceu:ʘQ Ò%!?]$96% Q&TzjFk>g'hVIo/.2Ŀrg 0+ 5փγɕ"ʀ ĽYZ6B_# ~9NH_Uk&~]NjfE IJ%Ei#&r#V{6SLswQEaݾSX9Eol"H8pwEp>3I|@)PRA+Yx̰O`i/bv'j/C; SFLS!) dġg2ӉzϸgR1pV :i{}Jm1bg2|qSItD+")Jc(t=e+AOc uu*{"2q-62{P[RZQ2 &#ß23dʘ;ܩn ޯ0$"!MA<R(YK%~ucW\7J}*8JJ [ݙ0:C~Pj"Ws ˆ֤O@5[4 /Sd 'Ё6](QB@ei?Zg:HԫH u` )IPs(«ң 5!Qgs:J}}}焸-$Sto 53"B/Q iިl>ڴv΂tK_:"`UIu朕l.8k!U v1vrHS*CtO!laqj~0Y4h. >蔍 NY.OA`p qjwav{Ώͽ-M,; QdiDczT迯ٌAfARϾFoB>d`ot#YZsoК#]m䫊Dq.RAjciM'^ZCܬ4~H+/"L"e:pjta1bq0'=9u==tyM_@?Ywy.w7֝0;tƳmGsw}׏{n)IS& ktH.}~^og{ۻrkc[n=lm[5ks,1_[F>~ m+]uswgc[dAؒ{ϟhf,Z~ HZ 3h^B&}Y8gANܵ4x I̱߆.U]&&{{operator_character}}]{{operator_character}}*| =>{{operator_character}}+| <(?!{{operator_character}}|[[:alpha:]])| <[[^\-]&&{{operator_character}}]+| <-{{operator_character}}+| [:@\x{2190}\x{21D2}#]{{operator_character}}+ )operator_character;[\p{Sm}\p{So}[{{disallowed_as_operator}}&&[\x{20}-\x{7E}]]]plainid(?:{{alphaplainid}}|{{op}}) rightarrow =>|\x{21D2}typeid(?:{{typeplainid}}|`[^`\n]+`)typeop(?x: [[^:=<@\x{2190}\x{21D2}#]&&{{operator_character}}]{{operator_character}}*| =[[^>]&&{{operator_character}}]{{operator_character}}*| =>{{operator_character}}+| <(?!{{operator_character}}|[[:alpha:]])| <[[^\-%:]&&{{operator_character}}]+| <[:%\-]{{operator_character}}+| :[[^<]&&{{operator_character}}]+| :<{{operator_character}}+| [@\x{2190}\x{21D2}#]{{operator_character}}+ ) typeplainid,(?:{{upper}}{{idrest}}|{{varid}}|{{typeop}}) typeprefix(:)\s* unicode_char\\u[0-9a-fA-F]{4}upper [$\p{Lu}]upperid(?:(\b\p{Lu}|\$){{idrest}})varid)(?:(?:\p{Ll}|_+(?={{idcont}})){{idrest}})withinbrackets2(?:\[(?:[^\[\]]|\[(?:[^\[\]]|\[[^\[\]]*\])*\])*\]) withinparens2(?:\((?:[^\(\)]|\((?:[^\(\)]|\([^\(\)]*\))*\))*\))xml_name[[:alpha:]:_][[:alnum:]:_.-]*xml_qualified_nameB(?:([[:alpha:]_][[:alnum:]_.-]*)(:))?([[:alpha:]_][[:alnum:]_.-]*)&x]iGo[;AbrCd<#lYJLK긧{ݲ,ao`9`a !@8 / _VuwUWUW1#KvVK=T=u}?co:vz כ?n!m+A"?o%<7)kmlwhn{`wE)pw.~Zߨ}DKnzzB[ 5}ĸo_Kﵻz(x;b9d`dMfD0΁8W ϯwn3w@d {8   cg3E+Zmc2mN1 WM+9P&|tlh?l3 DlܟG28Ad^2{&<# .L-bm} 2(dC %kN~QK7d(OW E(1`%HΎ o]c!IU?L2`zHij`dFVGC_>yPa8K~6Χ)c[ "R*8iOM4_oM&]8]&{GºĴmܾLޘIu/FUuݻAOOCTmL+B Wx\oX g@F`](_%gp玅!d<icV SjZ'鸃iZ\ڬс|ױ `&(LQ.DhֆDvuT"fex+m@?fykoXmWgy#98=f͠p(L4g1 JySc&Mm\Ž:jA2k"elټ(j_1о7Y%u{xi3yUOor&(C++ oJI7eM2 H! v̨\ ^ZC*{A5ZOV/4~AkV~Cؠ74@˃{%E{Yz>KmJ(>c;,i_hhiE:q5YgH@G& 'f3*;R,Jfߚd]X3m-%*Ḍ1+jā}7Q?}d{@(x}07k"sMc o$&y;ijIAe&AELC\ Z&EeP1 h4G{z0[X3IouWC27%x# 8i*gM)%-fudIbwO֎ٹMXc5OBh`Nq @ifhvuf8=q 9H;R`kМ<a'js#[j$Wa՜ a2 p̆ Zk℻Lu!yv5K~Kq|0uryoH02v|Ig}8ExHya3 a$:RH3z3仧>] ;|.ͱrjl~aAxS0 PII U*fM1?][RFvV7Lr2*WגY* ;ȡ6Q"s]0zm5y\x88W,Ǡ`6;7Վjf&ySNV\|H7*.3Lm$̲g`0Y}oC7} x[f|(j꩑u&W^i,o / '{A*_V@>8rlnvTɳY._Z]r$pQ`ƳˎotlL15m !HlC)UkzĈV'lH7q%Jr؉;fILn]DP:@Qqn `dK^c+dl~Ƕ#3tX.DC~r+e{N*t^+|C.-t]|dԤ5 ?do՞ҾɅXg'0Շ C5dKFȸGFv( s'vbJ2 P' Z,T J㾋v<::] AkN-"VڽCCڶa1ˉuT JO{h*lHc@}L?gg(LlrV7*Zc OE GLDT-xxo]vǨU ͛XOkc["#B 6z:HBk2{q׼fMk|GpvPTR+We4' q61oojrvxxӎc^Qz/9AelǮo h g!Ķs;Vro}*rVM4w∦hx[G\vS:hڑݹ:7 j^ ިwѓ+}>3θsLɿ( ;cAOI0t{={=sӚpcjSZ1v5 h(& c2tէȵԭ. 'B1 `zMvt$kּ0MENNT$fy4t: A+k^B @`VH ?|XL/ I7%FRȬsY`XJVU(y6wnC5DQvz q_ 0[|t15TYEERtaoJ⯗hR[^Vy#9nyǏXqxM>̔{^ c a^f sw @|&0 6qb̹#`.2cτ!]"F.QN lQ]U/Q)ѣ(  T_,V[T9$ZpV[1Ɂ~*ɧxΑYRI'Cv%(_86EE>2R_kk*V4*rDȕRr*MF7N?Hlf2?^aJ\vd)ګnϘ/HƛMqٖ[\;OGy0V.:F}Xj 0K)G3˽~jMF2M['3+RK- $PD;/]+1ܝmq=:e79-5[vD}g'\8>e`f&/E OxRn8};ņVef<0"+NVRh3&W!(cC!9*[oCw0&D*U$T*@+ޤD,Y$T,hM8.LZGۤR9Dg H$ I@ҀNS`$HQvWIjᕫo<ʓ7{2@SzZ *Ǖ78KxCx.XhL{Ss /C<[af 7]Mᕋb+(q:Ra97qp;dB*N.eRP+MC8Q^2<޼rRjFk0$'>!; Ž7? =.zk? 2Zw k u<~4k5S9:ҳZ{fl+:}\ yE,wAV8=Dd^[,GbXn)\q WIH@DRE5[WY7.x#퍐SsJGR G?kGVa%*KT z%Z]~h/"L &'885]qB_^pα32pd;P^kR^ЊTw~cYjⵊ%2v]%uJƮ* =b=%SZk160@{)g&/xuTN-ꞣ ?P~ST(͛ʥ|AgmN g,V֞"=iKiIkO"6{9ګ0p#"{oB{VK%J,XԩjH=+RE~AxX/, ­幎b-"!{ =MU;mwg;3*rc#U(]RYB*, =sf ͊R4:eJD@bUtH7Xf!wgɷĥ9ΆQ}\j )@ x;Ny;G+[ϑtd䶊td(ґ!R (xeg*qd\]..9Umesa8|a`&X53J4=tpc+!?DH թXªR2t\&\AN\G&@T#2b+3EU_Q!S@RvJ,@l~w$5n!a` Ŧ{tߖykM;ޣ}0+ aŢKuC5Ml%zb&898hSA=]2m;v-eVux!MB1^+38 qЫp Ɯ7 L"nшzߦpf'K10Vl>2\HmԎll{Qa?G`yr$[4穁;3\Sj*{x@PQ5456+mD]=:k%IJ)YT[K xLgZsYS)d3J!!h,mVK6CD9Yvz]Tl܅$E2\viȆ ^D2f8( !/s,:!كxv LHg,nHm 5DܥV5Q [TcvAk먉mlb|~1p:*וBi7_g6GqvV`kf3 i䠹dfP0Gzs'"pۺ@."{n0uY}690}theߨz '2zW @?w6Q% 50:TGM:HYؑjl, G7Bourne Again Shell (bash)shbashzshash .bash_aliases.bash_completions.bash_functions .bash_login .bash_logout .bash_profile.bash_variables.bashrc.profile.textmate_init.zlogin.zlogout .zprofile.zshenv.zshrcPKGBUILDebuildeclasssource.shell.bashf(?x) ^\#! .* \b(bash|zsh|sh|tcsh|ash|dash)\b | ^\# \s* -\*- [^*]* mode: \s* shell-script [^*]* -\*-  call_token\./ cmd_boundary(?=\s|;|$|>|<) extension\.sh identifier[[:alpha:]_][[:alnum:]_]*identifier_non_posix"[^{{metachar}}\d][^{{metachar}}=]* is_command(?=\S)is_end_of_interpolation\)is_end_of_option [^\w$-]|$ is_function<\s*\b(function)\s+|(?=\s*{{identifier_non_posix}}\s*\(\s*\))is_path_component (?=[^\s/]*/)is_start_of_arguments [`=|&;()<>\s] is_variable*(?=\s*{{nbc}}(?:[({]{{nbc}}[)}])?{{nbc}}=) keyword_break (?![-=\w])metachar[\s\t\n|&;()<>]nbc [^{}()=\s]*start_of_option(?:\s+|^)--?(?=[\w$]) varassign[+\-?]?=x= FN-PrSZX 2PhK^Z m%Qq$c$! >ƎhF#͛w͛ 1Xc3:bzV޵ 1#!XkuIr4h]P 3;TJS5:K2;X" :礠RW>XZߟ > }cp$ppBZGLz>癮;FÌ+˲ZQcp $#⬬6),vi0wF |̵acib؄^IY{FPjhueF Bxr.GΉ&s?Y/1ȇ<‚ZpAiO/GO?f%xK#r(CiBtR1>#J'yle>C7Uu "ӂ>y(Y.|%Y m;]#?D!=γ ;`qFl"CXad=?K—&;ˤ c"TK&lD0e~> 8SRnjkr{wh>!9%Ńu#R<z.i:sh!n$DlV6i6w+na]s+}|TFh\T*eR QhgCD ŋ `[/iK?͝6әBeS2~%<ӧӇ]f O hő1~ \M m7!1^ahA A Jp 6oD"±/fS4Ս}`ݼj57@?l=0a'dRRmnRWbx1Kn޾Ÿ"wx0QTձ =EƲs_j[l46j՚:U𛔤')SM5X"Go(.m(DBg~IuL@G<^ǷJ; ävKjŌysLYW%Z,p%6LBj(6#DRi% t^\.w5rv9:K a;fo8jFR20%T\*j_4 ՔUtgf,4t4֊un:ՖG_lb``mjV\J5T^#f$qX%{%ef[߭7u6 2ba3 x_S;\iVe)8N9O1Q[v:ZS+j8wh7:F7.&9FvZiCbs' A Pmp<&Y`2/a\Ihi6\t彆O@1 L{ʽ]l= 0]IB`.'@'4T3v{xRUk[+765m֦ߨ꣨ ^`=r{`C,޾rob\(}'+CDaoRdXiO,I>0Gsd蔦VjfU[$"~-T ' l5ع 3>ѯ%J X@g6&anqRK 3ozT@)zX큓X~n Q$˺6fG]tDgKOiD+rERNZL<MPi\cs?wy*XqSS:MNXڥZΥWICls.ur3sp 8R :OMhij OP.D<*-2DAdu6 Y4QC33Ū?S,oD]gŲCJVLߕLr;igK`s*0'ܰ|P{R @*d&%>X<=2YPփǏ\$<qy#^kȺw 7=f DV W% H~q.5 yZ!B[b[ h͞d|VSIlE8yƚv)DnTD]2'UPWZ7)$E`*g:N >41"Y+U`IF4G, so֐ {s3dP:ͺ,z3y.K{Z;Sc9~EV&F0ySgtFYE3MN.P%i0u>/pMlapAsSr}VЩ:w.yS|n–7q$Kqs ^kRD(}>M Y(̙ Ҡb"a o17 h~EI{dXV;VLKHZ}HKC9kyYlẉl̶ͦw-i7dNE%[xOD̥;śrT;bnh1>7n"zZ>̓ecΔS1ڠfbţrM͑ ~Wr21$Ԍw$bm9- c2'!ަo\RM'mfdgz: X]Z .&Q։MJG8 he։'bqN$osG DDČ Yl9l[cӋ-DdbēS}S).$D6 9l!VjL׷aryJ(Y<t*,Ҧqz5+mE?%+>!~ֳex6|q L6'B]H">Fg&RmbJ%2 #b8S͗t@v?M5v-SC:C$*c`tbTx< -tfD@p.#ܿ3]ن>KGłX],6x6>67xzXGkQɑW(^ c)gn9l@=c ک%Xdis\<""OTM"r)G8).Ƃ¨6{{m1mOLT}m^WT~}EEN8d OC0=4Գͥ1{%SpyaJF҇i#\+U'FtigXwk2э>Y$d׷jȤ|]{`zRAV>E,fzݍښf1ybEkeV`u=N+)gT0iR"'Fb惮hv#*&=#*hJ4jkSJsWTr|q88HtњVL7#&&8%~>w\_}@*u@LR 69Ey L^);7SoC CBUJWBJ@$Z Չ)M-I۷+lqڔ6";US$/_Z^gkYP*z/}mN^?X[ު;Y߾IWH7lGW()Tгzᾥ^ڣw?aF>R!?\)H A?E|m-o/.e畕 XK|dʎ+0P,:UTQm&~"`Ω'[-d{j],Kh&3{V'7Gs:a} қ( c˶$,c?<!x Sҧ5'jg]n0[{EEeMt6g32UԏE0ӟ`wtH4_ bzryVXrRRV<9K%~u_ \%8))zR)N2R|6RO'Q%*/rtlx!JHAF1Vu+ i46חdcjDJ" <%Ѫ?[YX Fn3:Z~cOa4&tV5v=k}$<ٿ9y'γ?S(R[\~1 Hvh/$%n<+ims-)>_ Μ;vk_h@d76K 3]Љ'5eSWăY l+u;:bG2Ae!6:?ŘY;hkY|gjOmy'έTǥh?aLN{QI {v|3|<|\|)x]{UB ! /@$@ !8 VIk`ʣ?{{I+hO_.MMBІ4OE"f}~˘U eJ1t-:q!x۸ըֶ\%;gKJ5zܑU3dVtlM;i6"(p$O拡G{(R;#-Fzy\h&Eg:]c.|:~ |:>ɧӫjEg/>Ago>/mо!@~o§'ZM6}Dk6"Lg6h'˴TݩR' /~zB \x4C%[lfCaԩ-K8Ηь 63~Fa#Z8u+zx$r~> ['l~p!.=j51xAßv{Ƥ{lF"qq!z.Uy|E@"1kʶ붩 :!&Јu"VMBIZSMN k4ѝR8L_,oAY_<|^CgtJ F CŇ.ZSV*|q-BQ&۽*_DA"F7_D!?꺬2-l5fЈ`_I 1ԠdJOLӃG 8= 8&} `Ob',@> iizly;gё0ydž4V܋> _ Pd1a_r;i{'Xt|{װ M_Z{;c4,2@F 7w66B Ⱦw6\,=R{XV DŽxUC4KM aioR0t vp)x>49hXiЗhX075&2M7k b$ Ê=VXXzop?a&*mzTN,ҋo;3X  N+83o83-1Y~FMqRս,K^St.E6JEw|pTvn!~j!᳘M;픭bX mt+D{V䴢"7[3HnEİ5\6PDoH&RâYSePeEE$z4݊aQLM BmYSMPMEE$t+rkQ~%݊QTDI"wҭ}EE$ٮt+`Q~-݊j+btz^%Bno:v[[GҺU]ppf}s?qvS;ۛC͘b'[5)[B(Vg듥çMo `EϰFUvUY#'3uu#>9Sd]\'B.ΞuWuūe!blN404okFղiNԲqdZz܋Fjel"^01N| 9a,25xlL+1"WUv+kfxl%>ĖClo!Ǿ<]ᗵV6ۉі8ʕSsvI|ҊHqI9BX-SfV^;KfHf9RWW˞9Qr컥>nZ-=kfhfa0nፚūeJ+W-O~C̹Yяr ϩsPNs`LTHR=/r"C#؏9EՏv,{ 9"'9ΊvxH! !w1 ~{Ktө-;dsӡuQ4G|cćpl{#ěR'Kf~+W2ħx!N=!Bm#T[F8QT:s21OC,\!6m4ϙċ+'b9%Bn'X鴴'׋#22sUի7K=æoYeT(!SApޜhEcc-,%⥢D*Z/īeiNb ca[xa1Y@7/4XD^-;mš|є&j iӄܞ &A~-2b M^ܥW:v̜5:rw7zM{)w3juʪy2WAJ Y!2畲ljEu3y:"u_2ϥ]݂Υ] 8:d)N@7*eCw,C+U5HDJYFr0yZ\:WtMjw0(,afT3ygSMn x^\IVjFf+mw캗{ QuwNRf;K&|H2.f&_vuLK.[O󴻺<ބ>}[\^je/Ԯ;{^Vd9Tcltcl source.tcl end_chars [;\n\}\]]inline_end_chars [;\s\}\]\\]most_likely_code6while|for|catch|return|break|continue|switch|exit|foreach|if|after|append|array|auto_execok|auto_import|auto_load|auto_mkindex|auto_mkindex_old|auto_qualify|auto_reset|bgerror|binary|cd|clock|close|concat|dde|encoding|eof|error|eval|exec|expr|fblocked|fconfigure|fcopy|file|fileevent|filename|flush|format|gets|glob|global|history|http|incr|info|interp|join|lappend|library|lindex|linsert|list|llength|load|lrange|lreplace|lsearch|lset|lsort|memory|msgcat|namespace|open|package|parray|pid|pkg::create|pkg_mkIndex|proc|puts|pwd|re_syntax|read|registry|rename|resource|scan|seek|set|socket|SafeBase|source|split|string|subst|Tcl|tcl_endOfWord|tcl_findLibrary|tcl_startOfNextWord|tcl_startOfPreviousWord|tcl_wordBreakAfter|tcl_wordBreakBefore|tcltest|tclvars|tell|time|trace|unknown|unset|update|uplevel|upvar|variable|vwait special_chars [;{}\[\]"\\]unquoted_string.[^\s{{special_chars}}][^\s${{special_chars}}]*var_unquoted_string&(?:\$\{[^ \}]+\}|{{unquoted_string}})+w x[{s'8ǎߑ%!妞x:L4L J# `ԯ%N"A{{;nג|z0\5BjqEpm^N6Zx1Q)r'rڧrgrl9 JY9KJy9+J{.=9E܈/5΍lwmv99_Bz(P?h^0cK{۔8_ C<%ؔ:!$Hj[-9;# (0"/#x(IAEmIh{HRGJXZJDX)S(DLI= #;?4/`*x)!؆0/zs]R蘎gu"˩qT43z%h׊&"+>'ۏy6(GbcubyH}PiL7v_~z(UMM~K7:r%$Ԧ(t$HMzU/:b/bGxn4HvwXuhGlVjʣ2G?#rSwn6G eD(.6dagqo-2`cdžob#ClL"!dy{ɵ= J'W=fsҷ&cRfhQ! Ҵ]#8V[7DDP#`zNA 0x >Iȥ.a/"gHE!:B>ģ(Z <ߞb'ՑcChR'=Bqva4±pNw  '$1iuI1 .=U|9oOS~qWlLhMhVa"QHzk=($Pq Pm|?/n}}?+:MIȞ[(w 877HӴ :p'ٚf:ù[ Xեodv\iyV%>Vuܪ}QryǿVi«C" %5 IzL 8k $/\ϊJt~ki8Y)W?D63o-7+&sR-Л)\c=u3CNDԴC̒ڡ=DyG*׬̓ Y褙AS|?YYoЏM?- TN9rlMȡiԏfw&zC̓<-.vcC844:LcG͌B+Y#ЍR'j P3[˿xzfvi]MX0UyJmYbU^XjmfwfjGWJ8rh Yp*ʇTy$vHb#% qW6X⒕~*2KW1RЅ47" NB:W!du9@*6υB(Dn5-D<[7C?¨g:/jErTW{CK:M nrRyڼ]D,d8r5M'% Ί,yX{:t1U2姥Ri|Rnbnؙ` TA j)N1زvɡCYR yn4 D`7,o& <-FYF%S:1yqo2kP&Nm~ݹZ12Zs&tS;:u{UGűBQ{C ]*eIN2P`*..5*ݜg]靐M+oq d fP9)GgZ]( LӼ45π:Y#z}L-*Cf?0˭Rn·o)- ٦tI&n PW?ꕗr)^Ӌ9CQ:y{utNy{Oc̱:gA,Q+PsU ]Ά TKIEnrz}-"0Ff7O~΅{Zٕ+ye3w~w)=xrQ O==fPb^'2h/UʞAtW.ZyU>*AM>:v(bEk땂!߆VPJVJe_ r7(AtU]7 4m&.n THP`[29@ N72fwQoiofV81mUo`IWeKhߵ͇~;7ۭvzL&z]BS)⪊[|W]?f͑Ȫ\ېXrE -/p=-$>Um 7SQHpO&Q$4&d]2`C*`;GնҙAp9( [6 jctWS[Lk6^ՃjG:$Nʆ- ^u%2uWX*LJۜ Sܱa S=曗X47.ru_<[չ.F>K:^ (w5tk֠( ]lE`@m$,ڨQMƉ?Textiletextiletext.html.textiletextilepxYio6}ݔd-$Vu6Pb +0c_&-Ӷr%znVz`AQ&@K$a$×KJ\>J]]ܛ@:lɑd2?2bLfX'&7ô'#A6vl:s5_lxpқ|4O?(jjENϲ\," ]+u4J MG0m)_ݩlʊ*"*f(xitgJCIjMN5 'HEl/n avGDO0m]yI,U9a8CژL~tUMW+ =ܕmaow+-XphEAﲜslh3g> MjA ʄot\UAH$LJVԒ+u¥J u"VZvɛ'?GkJdls?G38BEndJ;sHxBۉ4JiOc)6 E4fѓatIME3xMڋSv)E5IR @W}N31'Z1I.%TaTu $z:Ϧ|i>@Libe07Z`3JaԪҾ}8ΐXcr\4 [dCTڢJuRЭ o%lШsB+gڧ C:٠X675Pm|˳fM dn0I,(S=H)iD8GT"mQ5曰m" זG:ObZ1e!":bcKqq{$qEzX!D'26QJBY1_j\A ?uϩF)_);`z;|7 VOKEk qE}@STW Bs{c y;1u^5ѓoGkGY}Y> lF{--!%E:?pY6@"9@)Pe}].= tQzGK܄.%>Vzj̕E;{Gw`mo$Џ wzL̻ Ju p)U3܆AT&އ._Uָ nκc~gsߪÏZr5*k4P_Rv :|h$T^!6K TTtр(ppD;w,9VuHJkoyeQXML xmlxsdxslttlddtmlrngrssopmlsvgxamltext.xml(?x: ^(?: <\?xml\s | \s*<([\w-]+):Envelope\s+xmlns:\1\s*=\s*"http://schemas.xmlsoap.org/soap/envelope/"\s*> | \s*(?i:])) ) ) dtd_break['"\[\]()<>\s] identifier[[:alpha:]_][[:alnum:]_.-]*invalid_dtd_name[^{{dtd_break}}]+name[[:alpha:]:_][[:alnum:]:_.-]*qualified_attribute_nameA(?x) (?: (?: ({{identifier}}) # 1: valid namespace | ([^:=/<>\s]+) # 2: invalid namespace )(:) )? # namespace is optional (?: ({{identifier}}) # 3: valid localname | ([^=/<>\s]+?) # 4: invalid localname )(?=[=<>\s]|[/?]>)qualified_dtd_name{{name}}(?=[{{dtd_break}}])qualified_tag_name0(?x) (?: (?: ({{identifier}}) # 1: valid namespace | ([^?!/<>\s][^:/<>\s]*) # 2: invalid namespace )(:) )? # namespace is optional (?: ({{identifier}})(?=[/<>\s]) # 3: valid localname | ([^?!/<>\s][^/<>\s]*) # 4: invalid localname ) x[{W!E4A W!&*^ s#;VA'k u>Z[Wvǻl=ֳ33.[s5xT2MQT2:MdZDdy"k:MIuCv4ӎfpgh21<Ͱu[szGmbʅyoJ|gZ͈t,xޢHݱm[vt5R]y(5n|E+խf{J DK@OK#p7~VT{V@S~C]HAC%;Ru& `Zbq'n7u'B}h̦mD$f Yh.e"p2+`Kф2X< PBSU7uWwu$ͯ`P7;Pf$//ySx\I?ZhfA7 P5 A}hi^wtS¼]bӤ;V4Z*8N*ūY8aPQ/ E~bcFb24u-nl=%BKpr,.1lS2B9㛜N"D6ASJJy+W2^juI|z^Jx)$xGYhd#cyQ4h1t =^6ŧ*gA<Ƞu?h!Ne3ILAE&X拳UEonmT6<51 Imki#`=Ma`=Z|R. D$8j'c Of$J;{(1l!v4CtTP ҁ+0I\*80=_:^bf켭 }HLJ݇q~.^jM k&Éۭ/hw8zvQM\F> s?.jL[X1 32i=_uQk?0q >w2);?P:'a"YcCtxCR b%ċƉ'v 3PEh;L/b?CJځ=spPU=`+1V3\ʤ5e@;C0!GT j'\|gR ̑)d ӗĿҜ7ZlՄKkЛ?A(&qŖ١ٺ7.ޚ^qK wOt wSq|]kEʇ[g^pJiiثk Ŋ*SZmYAMLyamlymlsublime-syntax source.yaml^%YAML( ?1.\d+)?_flow_scalar_end_plain_in|(?x: (?= \s* $ | \s+ \# | \s* : (\s|$) | \s* : {{c_flow_indicator}} | \s* {{c_flow_indicator}} ) )_flow_scalar_end_plain_out<(?x: (?= \s* $ | \s+ \# | \s* : (\s|$) ) ) _type_all(?x: ({{_type_null}}) | ({{_type_bool}}) | ({{_type_int}}) | ({{_type_float}}) | ({{_type_timestamp}}) | ({{_type_value}}) | ({{_type_merge}}) ) _type_boolb(?x: y|Y|yes|Yes|YES|n|N|no|No|NO |true|True|TRUE|false|False|FALSE |on|On|ON|off|Off|OFF ) _type_float(?x: ([-+]? (?: [0-9] [0-9_]*)? (\.) [0-9.]* (?: [eE] [-+] [0-9]+)?) # (base 10) | ([-+]? [0-9] [0-9_]* (?: :[0-5]?[0-9])+ (\.) [0-9_]*) # (base 60) | ([-+]? (\.) (?: inf|Inf|INF)) # (infinity) | ( (\.) (?: nan|NaN|NAN)) # (not a number) ) _type_int(?x: ([-+]? (0b) [0-1_]+) # (base 2) | ([-+]? (0) [0-7_]+) # (base 8) | ([-+]? (?: 0|[1-9][0-9_]*)) # (base 10) | ([-+]? (0x) [0-9a-fA-F_]+) # (base 16) | ([-+]? [1-9] [0-9_]* (?: :[0-5]?[0-9])+) # (base 60) ) _type_merge<< _type_null(?:null|Null|NULL|~)_type_timestamp(?x: \d{4} (-) \d{2} (-) \d{2} # (y-m-d) | \d{4} # (year) (-) \d{1,2} # (month) (-) \d{1,2} # (day) (?: [Tt] | [ \t]+) \d{1,2} # (hour) (:) \d{2} # (minute) (:) \d{2} # (second) (?: (\.)\d*)? # (fraction) [ \t]* (?: Z | [-+] \d{1,2} (?: (:)\d{1,2})? )? # (time zone) ) _type_value=c_flow_indicator [\[\]{},] c_indicator[-?:,\[\]{}#&*!|>'"%@`] c_ns_esc_charG\\(?:[0abtnvfre "/\\N_Lp]|x[\dA-Fa-f]{2}|u[\dA-Fa-f]{4}|U[\dA-Fa-f]{8})c_ns_tag_propertyM(?x: ! < {{ns_uri_char}}+ > | {{c_tag_handle}} {{ns_tag_char}}+ | ! ) c_tag_handle(?:!(?:{{ns_word_char}}*!)?)ns_anchor_char[^\s\[\]/{/},]ns_anchor_name{{ns_anchor_char}}+ns_plain_first_plain_inC(?x: [^\s{{c_indicator}}] | [?:-] [^\s{{c_flow_indicator}}] )ns_plain_first_plain_out,(?x: [^\s{{c_indicator}}] | [?:-] \S ) ns_tag_char7(?x: %[0-9A-Fa-f]{2} | [0-9A-Za-z\-#;/?:@&=+$_.~*'()] ) ns_tag_prefixN(?x: ! {{ns_uri_char}}* | (?![,!\[\]{}]) {{ns_uri_char}}+ ) ns_uri_char=(?x: %[0-9A-Fa-f]{2} | [0-9A-Za-z\-#;/?:@&=+$,_.!~*'()\[\]] ) ns_word_char [0-9A-Za-z\-]s_sep[ \t]+ xRHֆ\=\ 2b@vvv q*K +lTcKY&}OZ>lLMPe9}K^ǭ\0l8_aBQlS 9 Mc0O'F 9ئ횶Duiq/|hИ)XXXEq#Q5(Hrzy h袤T D"@Q;zժz]o|:),h4rvVŐ2d2xCv`bgdgD9 WmyGR4b]ib-$KŨl[#YLbLgYM0j+4+և"nF݌x1y}GB;ZYʚ|ј| U!+=;};Vt4Z&'c&YeySk_ZWtQ.ꭲ^A8O 7q[;0] 1 7H:H0k5 vh}B&ܦ/6\5W|'Y)E=Wg 5Jy-5`+܁Ǖ{ڍOOOc~\U_.w+JKEC:\_b9Zˡh%;$upGv>C!k;;-z8=Y۹l'*Mk$(!kM'H W'$Ȉ$SLD 2dRhY`uA ^Z,b e"ĉ(%W+8|2t~2!ֲ̰ K֣Ui.POJucn=ٱr]e/Xx$$fgc/yV[ #瞑8 )C:#U:8-: ƞg]S[7T[qw'6sɐDGM;^(`q-ƿpN@uN( FKC4;~}%:4^']̗FyнnJt4r]!-}54Ÿd\i #!qb Q qoqQ-Ⰱ~ RyT[I+]1mw):n8A87Z $ng&H$Ȣ`L0,a%)&#[!Is*_;PbJm=0+M/+Q.Xv6tR[raN$>ߝL &qNBdd|)ËeJ,? }6?[+炆Ȥ^F^Uy@ߍ0n~[;A}c5҇|\qujzRU$ǣHբ~BÚ֊u`B9.aH!>D]G qNJTP'ϲ=swW̦EXv "Z̭e5h1I"p]e-D .?)t& }UpuݕVtɾ;RUUJؐf@0Oz'; c:6s&@Ya^{0lg߶>Փ0G$@}׷Jc@X͏ޢzk 2pYf}©IwȒd|ڭhQ?n_Deݲxa9UI".swmiu sfKdd[3K{7_S(2熕thb|D2?KUBZ1ԤKcݕ1L826ݖ~5V=yOt:&~?6{z˷MvMCzߑoFo7[1coߦ[)$')=w vQ W Jq¨ ҺU(1*wD|kQ]%"7Aܨᡆ ~.J =V-ꕀJHnF|Z8Zbx.EV #[v+z nH+Mfn;b(;!-@A9?±Z^-?0iAs9$DEy-8;kD%@EWذg G ݊Y[Xom!"  cx%X2٪^5'˳ѱxKq~q0!=';?E0veq:a ky=6ܠ}&sT:*88`Ny km`GEe9q:_Xmr$(@[TiI :tznm*F,>Lz!)Z_$! @mcCU5e]XSSo *݈a['od-94atN`#"<6FQRB#K|qG%s,\Ȉ$ x1~Œy!RqK\bgvlON1ib ١H!V}Q/zh;JܥZ)TݑRuDWf jdx3HhqS .'l%`"|3-ہĀe"H:Т8CDž}j&{v-_iAO<вLGŢ ̇Z]?uLV7YCEFe4*(+i$TG٩xRzϥ^d~רߛ][ZBuO):{W_ȒPxB&=?A{%Es5eV)wRSWr忩[ByM2ßE'e]23L8^R.CXtnUDiD=Wk[ t!N[R4ȋ5 RWNֈlsO ( plhyYs4LwH4OeeO!2}-kN!9`⺉y 0+l1t>c0gĉò^A҆E+j l[Y~p=;/gQ{I:ASǷrџŞ'pD  ZY~;e?mʻ9!⩁'OIͳ2FQ'j0?IyI|.f؊-߅tp:{J\'2aZ W;xKO=6|jR;2UF5;8zGu8ګo9uRA TTUIRzge]T۽zԎR^ը%͑5QO[A焦qF:nAdaadbadsgpr source.adaxڽV_o6E! kޖ0QjP-Hx}}eҖENw2ڎ=B6\/^uGrwǎv#Gv4eɄ~c^Єm 'OZ4nasѪR[rDfeY,<rqG (XNXι;>V1n4vN;7;VчƸT?2* `>'4ʋ_H,"y7v‚ gTqOj0G ҂0BdʲZ1B$3 M"8 xsx ~#kfh5 i߽['_g3:kSE[3bnLId!$zϰ'6ya' :ׁ )+P,ER@ƞ"A4 ?Ƭ߻nz..@~%c*(xz"67Sw0fEH1\O_R)EJ*ϺZ]s E$=4w8Ư&(>F?ҔU[q( J P,la% d,-bH RM }3L娪"g>+!YmLd+H*,g-K|ՉTH7"]))??9YMzvB Apache Conf envvarshtaccessHTACCESShtgroupsHTGROUPShtpasswdHTPASSWD .htaccess .HTACCESS .htgroups .HTGROUPS .htpasswd .HTPASSWDsource.apacheconfxrq嵓1رSe.[#S$$̗re%0$'1i÷Kr!s!9{\ؕJBwc====VOm|x$N#|Y=ͮ/{?GF#5FE2<ѣ_h,K,YȎ*sg[=,Ҍ>CJz>i1_or{@Ro4N`b<ޔlrߺOg 4Kok` & @v}b>Is#_ :~xn~.]y]Iؑ* `X4i 2S^6K!deH]B-O>m96f)m9Q]v2y(4R?[Dp‹W-m邮;A}=_; dayD a<(O(Ι';~i)Yr%Փvڽx\pnG&^̢0LA w <\5Ҕ JX\O@[a<˫VC;v89IzHV4>giwIΩHei&f][+O}7`&6q|hK9$OZ4Ni-(Xt5h\D{4Br3VKC|VXX/ӱ&Q]G<9΢$}y<_׹zA(BMI9Є(ϔòa14uIO(Ck:fph'$}E}\ <F>8uj,wr=I@ssG O4 Q7wW%m(fW,԰ KLBt"cV~ HzAB? 9J5y0I}ȧ 풐#bB@ V$~ET+/x.b Z`ى*0}(wzMCƨ$0R$ 9_땈.&> RvH}yU}! >1>X7`m:c>z^ UwLzم{**>).1"Nz:͇Zl ,08y€0:hU|m& >5ĨHq#Sr%.;|,'avo.l_/e*޵e?a>#emѥ*Vۡ*nrq4Dxڙ~d$TD٧Cysgc0sE[vVp@P *ػ?J7#)1Ԑ9% 0\wGdHzNU{E=ƲoV<hhVaAЪJreJf13F+:Tt@d%a-CiwUGm%DZfJVZa_ІtUB9M ]JXu}yv_0[{zB7B$Ip"lpzs(vk Gtś) # tg1cD5R<:) Wۡ+Rq6L4n~&D{OC|g`ܥ `{P+۽$Eg0Uj>] fCS=%M7,)U8eQhTM GݤnI$ 1DGW%iBح1V иB&y.ZZŷju $ D"*bHP*)R41IrKLҐIBIQF0Ft$SM#Ԯr*)u} Q;2Ԧd|Y}.e[?n0]5 Y9E%UX#P΂8Wc(raM7]A: si䦖`鈢Y,,(̵`5V H-|~c5 }9YdȖ 攟 PyhX(HٯXon(#$# ¹ ( 5҃0rQRNpYB`"nDiE@% j@ ْ;[\ @bM.aYi^|x1, c .&ayT&}E 'w S”irL&TKE \ٳ&"tv?VCCf[_MY"`!Л'+ {%c%*qfɲ Gt1ObMa)Ú)W$T &#ûXNGD>%hvT&4םԷ%ϟ&deǯq;cEPY`cW_d*Z*@I6'Qh ͢)dA较I pTN\e a2Kw@E;Is!t~ M^FmCm c-ۛ]{JQNX(]p+pHN#t-cߔ 3]+ظP+iI6DeePȡI;,#|f/b0s6.bmDJL#nE;HPB(0z[TUaT"j$f$)tLs+&n(G$l&ȏ]Ep*>3 ͒;Uq_]S,2!<1xVC)܇;Dޞ u!+&ŶPMI?*>3mW.HF\%xK5SWΒ$?3phu'D_ * X\RG/HHWk-\%qp ɃTp q: .XPv}&l [1 l1#M`}k{] ^}%gPF (WCj\U$kݠ4j"Cʜƞ7)*Az,aDH:dWm|Ɉ.-sOJԧI[w챦${$Y7IFh&>B2^H_?\\~Ev?Pa;>H} ~5Ql|9/գVb2Ϧ:p ܱ,r$>- 'g$z1rU3zl({4Nk'c Fk FN4l!bj\zudvfag`bfF94s܉cȓgc+ 4sFI?ƵP#b`d5&V>q$4 y&T;JnCb㺸u kV]~\vgbv:~uA_~ߐa}bj5+nW\_ +)+ߎ{CPDQ_`Ր$o ?=8R_i>{-(Cm)|gX?0 KD{?('2Cqq9fRi|{I7JI˶еojjZ;DEto5JAsciiDoc (Asciidoctor)adocadasciidoc text.asciidocx[{F6ڦw( "9!z nJoR_?6JVb}%WI8}?}}ݗ=3ҌF#4rudgf}Μ9#Nja:ݸl$9onkr]Gs-۳Pms3H<L'<3|?<=Of@w9Vl^ F<X3َ=$n6&Î~:o1$]hy.ǻwۈa/'~/{bqc$?X j;1 <ۓAa~˳zDL=oxj98Q8 #3'S`X'm1 'BP\3=8 Ӵ6 q=)Gfh8Fdl0>ɺ1t>(r_g!&+Dn ;#@9+ഝuLc] )7yƽ+8Gn]夜~t̮X$Z&zrpA(}Z3Zn 1Ywˍ*rF&qi kq]hct)4'6cop 2ѣCpDYNt9ln 3f93[_2sL˰mOm TҲdG0TlCo WBoF:#^ɏk%=PWSyK_qdku6 o90%+8ev0Ʈ{N (r?p*{ %Vjc$] OQL@~&H6l9&@rn9@3QMK-ӐHCpJam1z>dwA5@f-j-{1aD%IHWwg/iS" | Jo/!AO((Y1ViݯI~FUwZAjms #&:ܙs^mZC;"+ d8@@8pl/8pƁxfhEq9fL}\>+hL%O&,6mWxK0MrK.1#݊b"2~[FH}#*&?Ai&IS)\4,M eڨΗ4dj2MJ"%׶vF}"ѰX%ȍFF't{5kxZ-C,ҏJ'$=P-wG+.F[gE@u.K3Y5?M'Kebq!pYIuō;Z%XKQD9,Ohܝfmn}&2k%mFZ_(ZEl }оJs>u,qQ$vqs{sX͌Fk6Q\Hz$ bM VW =tce%4%0HP~ )Ӆ#).RM>Y D:HGv- XVZCuoaֽ,8/HG1Rh2u\4Q]ZpA|J\3c V>1/iW8-FZ.ow (W E(c,ɑ V l$脌pF?W|EMτN1A>kM.8kE;(4BOF׫_6eILĠ *dyAq(? 90Is>|r'4-'iEN:̍E`= j4ЮG _kD?"$_Q%miE_җ+'W ! u$h4 ׍[ecYxXML}n/l,!kN~:Vph,"y^A@ПVAП>˔S4@p=cݬ= (Vd@b|_/1Ry&U]>x`ޣ>^\x8u{]xxm3xi8SYƷ"Ж1v#r8}HqԎ6TaAAa"6*b.4xlYN\p` s/9"!-7P;Qw-5rS1{*vR!'bgGdGZpe\-(Qi|ie܋)%q GouT'KuEl՞ 7;9cD}0"R&hhߜ_YސfҹlC64kZE<[uLiNSpzrl1*h*hUA__O([>g7ӂAw;yASA-AMY9k4:ɺzr+ģPW[mN]n@*~D\-z8]`~ӳZ|tWˏ#B8'spF wV Zٲ85"c#bQ_`/u$#׵;C'N "? _5;8yuژ:/RС2zʎÆjf"_X\̚:9)oD9'~|IʭoIΓ/-~݂i&Kd ~cbI%eJ C3I9- 3W%HA n9zBpflXVA/uR eq6p}ƅ>LR}1 KPױY++'{_BzV1`a+O=58w61"ĎđW@f aa^2>Qw38.p%nQ)ÆOz]ÕɮG4cm3v! +"aKn\caJTrM8.p2opc {Knqa %Q1N[XưNAH t%ŲOV;{8+8g :w-C?IIAaojԩ{ 6<mCF!@ypmNq5-R=WUˏwtYH dIiř,^C?yc~T{.e%mWw^qXr\_z|GKYϷ^JAsk'㬋g]m7HPA0y "QNHA :TE@_*~'JE[or":!-&pRwC}mh3ўhӯo* jZh ` t5LF@ /S6 J2c45MKL3Dz?*~Gӄ/ͼl"/Llp?QdՄ)6S L2 WE!Uư6M3%z3wրA˵^uv u}իlm?0zGo[[,׬-pf8pה t(&>*_b$c)Yh/]x'Z@N:FW]k.ZR80!a{1zG,A4 /T;Qr7ErX[{7Dǂ_e]ῆ^y^3o갮]Ec[٘}Xjy>^9f,u܈NU̕}g/ʱ4+kd7 >& Ǚ\(~yzVj>|(? )@p6Zj0?$ӯj$kDG,C;{&A|8Iz(N?Ձ^儸~4m>#$pe'rtkt}Թ9t<)WQmAas $7(ޤ>PWLX;DcYN:  ARM AssemblysSsource.asm.armxX[snM;4Ӕۮ:ɲ;-%A .џDҢnsH|~zj<D&i^i,^7͹y湥yMҼMsaiw|fit\ZwZź]}irE=*߶ C>wnL8S\U_EjS`¥N cB~O[aqhfj|Qv)aIsDm6JVzNd5dolkTkvd\$%.OL27*x׮;1f~~`2fy&AR*!n|N#bQY6Ͳ "U /~C߭_7FP2C8"rFvajtD%#P 瀻 $E Gԃ>42) yBA7'ؔy&4b!@XTՌO4X \'WlgœL:)&IIejH` X0EԐhHL5LMxۉȾ TT4ayGR+ _M؀H1V掛+-IaRܥfgy-,m*5K!8#c֬!J%ʾF\ IkH֦:Hs K L&׆&J~փ:f`Wj&j&ףsUV$sҗ7WZ-ij8  $W vS#1 hwc%v05\q]Ѝ [ukqlJcO$C/CiFE'⊗O+j $Oq̸!+ "WO*t/pɚgqƛHU/J2TJ#nSc'+]elZRQ13곸6bqijJsRVc;]c !s32qD4W6:N:|Xa'wL'kT&IuOeCW_rG ؑMyZ|Lн؈U%ՆJUï\J *heG| 1 0~k./fP]=?A?53wKVK*Rm%@Z&'cҬj; qmπۉw꧎>XYLu_YI8?g/Wv#9X\}]^=}~· [I4a~;I/PW*AFBVv'%bVO PV[h*n@={qTwh?>e|w?-pM~`td(~z\Rz6Ȋޟ/z0w،jz\a*D41:+=3 >U\c, Lq],EQHĵ8DT;ISJYUZ0$3cpF z1zq%buʞ]^?܏'~LN _㩡XK+ R*x:rF2Y A:ot( Og]ix^cPwucDQKS){y2M8o6]|42 ,jjqPt{ 5tq0:WvH?:08!9}2 F@ky^]/Bk'GԍnN1_nK:Y(biK"4  v )AP$溶+Uv*>G H ߵBv-N2OvioE>A##P%9d ]Fka؇ä4s'!K= ifH1{"]g*[0Gi\Ǟk.dbx- й&4M&TͲ]\2*m۰Hp@KFF b;e]C#YG,Jbq)ŝK!xz2ű6MNgQyݶN=, E0rușDv ͑mx)1nNƂ|*QE(cq8EEI9LepzFgmZƑWvNs\!1ȏwG3t珮.\ $ ^JcNT UbLح]ixji `ZA `TP)~kڪ>5{$LM_n;4/F770IƯԏ=JprOlj 3R#uv?t9v/hh T5g<ĵ;j-m _d8"T2[KV+#aVe|D:|`,AQZ۔xM?xd9٘uTY%ӲhPdd4*'1 5EŶ ņop\.@]a]Ȯq*7m1 OȀ4+(?rGY8 ؐ} IHĀUlx&ƪ =6VM{jJ35DžgS ZiG.c^ *3ph)L?ŽKlgYR f&ϑ8/ӣ{=#ߣlq b(|:cBaiB|i6(&" dS?&AKyBkKp$Mj7*A_Oи=y#fc3tyTKdC&؄?sP$^u0uhMmQ]6th5y:֍Ӝzn-m$B?Ŵt.Țiejzj%Nf^ޠ[I^oVo٨I VA;C+ŎoVj}ޏE nc.R: ʹ n,XPe #0z*Ray^'ӈUB,֏~}*[!HZ&H![Pj9uz&{fEdG@ƒ 3 S0eܷyH k*4 6W^2RMz.!`6ZJCq 7B&WY_&Mhbs:F9$eaXF9t㰮vert8 9>Xmv2P6+ryƹ't-f mUtd~ |(bw,+_pt%L".ر=fGxt. EF1OW@_g!Qg뫳$Jo*q5O쏰ʂQg)1t*#| |RFEY@|5MV7Q!q&!08W h60⫂)2I>"TG,{Cw^ # >j"{(5OۓGz>9f,(*jG6ՌϢ4%V#lNV<\%gՓsNdJIZμ27jʠD$ qॴ:<{{>_C˛w>vPp޵Oo^?l%dsl#nK8ś|[}B>] S 3<7=Y˚n&=M{dixʣb{ir;6 xȇwDэb$$qЧ Rb,WQ*(2Ȱȫܜop擱/%WVI<>c 3Xlo /Qr|$ǽbz~&3ܝ% Toœy<[cs,F)sXpԱf'yOhEŀ1]f;1[!DhMyUN3;v: ޸(}ݣc]Df+KUqY&AĞt.#YR k@QC `rVXKAOUSt&= @>&+rmh89w;#vF g7prJT ϙAW\ڲݮ7E 38*Hs"W-:H([a2+ST  fXNُBT?._W XQʅH"΍)PXQJn)Kg$࿕qM!Aa+CB~tշi]1~nhoh_i2xMmX bV@nH_j>vV% T*砈Hg>gO&^C'[/1W䓡*Qw!D2n*^:FKhC~9mc=64 O9>1L$a-@Q)[ u< Vvm2EKg.ŻEpM=\t[w93NN9/_o988;cNS~qUDW8m|)))))))?)S~S~S_)S5o89sހ2߉>3:E:&b:İUbs-dwψTE)2-dgG zgΝg VAŗt/vF_Lj!y3iY(烑M"("b1d42 >MW8";_|L xW PZt7G HY&sj#iO`aGk˪zFQw%?i^"^x5fk^ Os;@@RlSDJ8J=ڌL|Le-&)[N϶R YԔ/G=,Ԟ<~%3 yPM?暋g`=YQȃ82. "1u˱`XMmW`[S:61ZWRxxR-,%ۍ0M!ÞR0?ca;"D*L bLE_O}Hfqk#5T?OU7#!Kځ6sABB<2QqWqT^ml^gQ}/z rh'P z $ BdtoÓ99h6760] C5P@z:"'Z7* @`D90i -ĠHHtOVGE"u \_SɺW4,UHօLYШ\@H{0\ >d$dGU)3g`XodAF7Nt` eϕ!8Da]2L븶/Z 3AK^ \;H9Kh)o20TdDN /k9W&PY@4/Ksn XJYFfLAL5ywDQ;~Fg4t}:ʮ[2ǖdݽnCuБ)[i9rIU&W% /::ΔhrtcSJA8; Ѭc=\NPC7ܡv\ ;D7j$ m~b Mgߧ+0*pޓ HˌŚF_WqN11t)q$QQ$pi1guM<;q KO>#ӕ ,Hy8]nWRqф24:!o n4ug(!A0 R6łgjPOez0SlXݐ%Fo)8Y˽[4 3uSH a.y@a`M`@ }&uAD4Š gD1%N_ x,`OyETs7d,-d=eRac+wKA(\#Pb5-$988p?*~^mB <7sVqqMX߱>z~!ٿaӤ^.byv!nF=cԢ佚=}5%0(jD0@DJ7 X_TO`H3)>B}Ʃ{: NC \%X_ndSQDaX'CcEһ?hjhh"t}V m7c@."@}Xe&B1Qd|Q c+$\]('&F΋ME&4‚e@3zr#(Ini'iRoL,fMWR2OP3A%Oyiqb d\ùaBgY0 B,F!(,T+cce}R>RL'T | ; Ds7ua;)f#Zn6,=2uS9dz:5tlI#P4q E[M#: $t]l)qg#d"M@9:<<vnywX![+ 8cXxT gEqIOknzQE؅ad?[O{%xR$C[!s =S~ghl5cӕxL7*aS{sQ0 $ =[vZy;O4GI8;l/5Nyj(K;x.`Y=$kFL`'+8c71q#tfQb4g49ҿbk.5. Q ǬiҶr V;dIˣzUKVV $0H߅ `Iܙ\ Yf3>p&2M}DA=~b" 2hjE;|~1X8ʈ_*VƉcC$^,Pc gq4"Q U˹5s"x$|GJ #2&]/_@ Ac}&MMaL% w? { .xB*K^{΂BpׇG5IgXq?otaeєG&+\Mjw ^Fk4}E-[blDhk h#}]: J-qnA\MFf} d#-Ua YxL,1LƛD*-Ğ-"m*Hۃ [V'N^mhgY?L F_'!#Ǖ‘/tODwcL?]:m&'Zlѹ-Yu. ONo~G;}f஘V=j7ft Ѓ%LAͿ(vlW]xej˚i {/HB2X.^8Ga-=XVOem&ÏQsyXcB0N6 75`-関{c_N=qV>vDɓPp+Pڜ4q5N!n CGia zKDW`]G85Sp$~(]k" z`lqa\ny2(q2y+ xh4 eF9TR^su E^JŮ; +%_ACҫM0u&f,\9;EAU+W,ivj54F6}Q7˯4irY\fYu2Tt^bht!;\I!nҍ!ٛyM#P56~4~ Dx!CoHX Xo⊌ٮ Fhٟz> [) ܟ)2R@Fw~a KJ#O=4x9C(H 3b47" nO鵭ڥF uC[!#.saon*] fDs q`vir[/R5^MSh᭮[)n*m~6ik^amڑ槭ZªuACưH 4Џ8 IIE:&B[IW?UuH&=_Z»*ͭ&%<2a }G-CubmSN?e# \G])G͝c=xN4MRkN̒8XUK-@d.r¾ I3 USyQ\|CF..Wy %ۅbyapBBGIЉ/(PҖ;V㎑5[85$V}yc,I`5w\z+[%/uܳ %G:Ùw/ `N<9"QuKw~@v;K'cvR"W?F.*t/VWuE׫y9iWy'[@xT@_܀ZE}^b) w`/;qyXpSB 4#xha0fC^1<o(. AtB!퐽IW.. qDwJ!QWtNQqVժwjBPE)8ib9$]udPFn^(uYJq" y33 o}5`9^Dٳ ##"~ kt\]z}0^p $NדC0cڀі&hv<=3ұ (.5s#g2 WĞk!?gj$4cÐh>/^BbP?3z<eA,U}bxmR2@7㽇(NJ[ `Q:F>JahA0V/.1+4~ɑ_I Ou>2{&6G:ŻVd2x&>{;GR|P[Tŧ-]N@(%[id#n@ﱥU9z)Ht^پU!K/nܼNn޸}|z]r;7[,B>ɽ޾ ;wo|{H L s<=D>PGIϨzFu;b9N@>Wj-^cjjP*TvG7I.b- , Ÿ*y)?juCMake C Headerh.insource.cmake.config.c identifier\b[[:alpha:]_][[:alnum:]_]*\bfxڭSKN0Ӓ C$Ģ]@ `ǁ(9TUS9V,b|^QkGgTH4 us 4̹7̹ؠo0hnJW}j !s. |)U~ d)n*,)(RqUAu|!̪8C)œ\S\(^(xYJsʔlˤJf8 Ŷ0y+:8 ~P0VφzU/*rx>r.wȚF>4smWpmOX?l,H&:+,hαlnm'K^OVvhl2%w>P:]d;CMake C++ Headerhh.inhpp.inhxx.inh++.insource.cmake.config.c++ identifier\b[[:alpha:]_][[:alnum:]_]*\bxڭSmO0`h_?,.])ذKW4Gf#JuswOoFn'T Q `P=0z `o[eql@ЁSNj@Bn ~ΗJO2\c?PlN0 霗Iy\zѾ%$ߓ^̲'|*$ >rKБ##cqHV wNLIUE)h7/S뿨xanBJ%X>jvQa'c{BLatm?IA0!%1tk!m=e* 4qw^8-92f% 9v.Y 9֮v̒<3wqj[*W=\$]) identifier\b[[:alpha:]_][[:alnum:]_]*\bunquoted_argument{{unquoted_argument_element}}+unquoted_argument_element [^ ()#"\'] x\SI}x( bYCI<0aN&Z_=ݯDզjmkv#NnZ6OO<π-rfAQ sBpӴ;k9աp8oMJg8Bo8SQyU\PѠ6 ˰uemfh_߇s G1icuk-r,8~ڇihMW kc0: Ah\)j4;k7lHmLh6PB1J6_lHNH&BnI!d)۴6vZ^ku[&-cCdJhccCt,860n:7ێtX#T[¤H11ЇTqN nςc6yώa5 "ǧ 4>fF  -`YPv?vx".,ԍAҜA^P:8 ?i@9Ö6͍&AZ2f8j1(<wrqrPbQ/y"H`>8&T ?q*1p!E^Khkz |އ~b߃meLw%( eaܤLcX;, +s'_ywЁ\AưzPnwx'M4/9׀0qc J;iwp - 4O0@dtՒDg(;B7K"hFKj0vݙeچ7̶nV;S76L+՟p8t˂vJ `hڌ׌u2PZ ||cR)Vv$='nt?e6(`n_xtQ?~#BYkgg]|LKsFEuZC'~2$ v]tT#4qIN]Ml!Pcr Nࢭt+JG|$gSZ.S X0-({}g ,`66M%95s*6P{}ڲng7Z]h%1 4>̴MFV}MǠUv e? ED8qw7.rs".iJBBt7{}CmG~TD^D8ER-狋/U?- =rvi\-(w]%Z{u:z8ƴқZ&P̠ ,]y#~n6AP YL-A{-u[rX_ԯI\EJ.%Tsgw8c5mX251s Fj;: kl*; x dieBur,gglGKhmbGѴJqF)"ޅ\SNӘu:+.v;%|Ɠ1IE<#emh.C"Y-Zxed׺]̀dﹾl0i:vH?vêشSqL;ȴyLIVr׉}7fnܑL,Zۆ>|M7&e3սaTk!O}iF9F8h70_f͐6OpSp򃉸˘P9 5C7~g h؁~11bĠġ.K")ذ[m߃9I3N0fg=C1CudZ_Uh ,8XP|(FgYp%K\Uxkw[CNBGwgNBh"[Iڥ?[lx8F#`l7 \mM>>٤w#ӌ9_&1 !w'3Y䤧/|HuѨHh| JM론斺PP,g(z=T{|b5P߾HIgw;q҈҃/3w0 b:_22L< oskJ( L$;* `vmTtsΉIq%=@DYdn%~g-6JtېEcdݳPĝy͈u9+nAkǘHP o_ "9/^odaO]{}!,_-Z"pN"^s/_e !I%EH&$*Ʀ}Kl6L B+he ]BhV(CR7HP %38G;חK;~bz>ZA-/qhܑ\Rq$VY-Rs9rg|V̽ɕkYjRRe:qći rnZ*SIF&}'ʻB">'ȞfVJWUj7Ew׹r%_*v J%AXsb9q=+s/XwCm~@wM%VljQ"$Jőd0-es*L#t?_s$.;<Ŧ'vݿ71ѷ$Yv;E>( vDyc5LEʀ= PG >ߍ}fnyfPqGdڷ\Eq5n'zw5V̗T|7<*Tk|iQ@%$t c Yiە: , }/YY\#ى;~t@<^IǼސkgג"_ۨI|RM:D,X]cQ]q087dߟzN}B%2}R=nQ3ї+ӰqLn}X?&JD`YϽ_?:"΀וa9D7>nȄz j#睞]xq]Ct{@$+kZt CMakeCacheCMakeCache.txtsource.cmakecache# This is the CMakeCache file. identifier[a-zA-Z0-9\-_:\\/ .]+xڭTmK0nw;pŠJV؎kMj?]7S/ϴVH!C >XBG*A׃my)\O2pdyǾDqx\v=ؠK̈lc'L(HǡQ0`GY }ir(Q&걪efkBI㕐%-x 'qK19$ V*~Kvd(?4 LVO/hesȹ( @Mt!~{s*kf+(oy|7X-Vs?ob?F wL>h23X|~[ά=]/4 CMakeCommandscommands.builtin.cmake*+x]u|G6i6M6mhz𮊼uJJ컾卣$1333333333wƍe?j?`̛''w?Վݮ7;/N$S!&P3qC!W[Bmu:DݎPgBu6DmAs!Nz&D݅PCVzE!xQR0ދPT}Et?BmC(AڎP4@! zҵzhE(jqBF(=Eh! ~:P4@A(4@BE(cEt h# zh@('A'=P4@O# )B(i:P4@gB=P4@AUgiވP4@oB(7# [=~[9! ʃQs4@B  ,B! PhކPETD(y*Q&P PhG( h*ETE(B]B(eZ@(E @yw  EE^Ed# ЋETG(%6hY+EP4@WP4@D(kEB(6BurJf([ FME =A#ukP4CA|h_A(W: .<7Q( @  Z?(S>8YQ4TQPQ0>}8Ѩ|ѨhT>hT> hT>hT>&9*>6YQ4>8QxQQDѨ|2Ѩ| Ѩ|*Ѩ|Ѩ|zrTߟ(~&Ѩ|Ѩ|6Ѩ|Ѩ|.ѯ<Q|QQBQP8YQ4QRQ2QrQ QJ|Ѩ|5Ѩ| Ѩ|mrTKV hThThT hThThT(O@4*@4*ߑ;Ec        J'Gb~$YQ4QQ1QqQ QIQ)QiQQY\. wп /:X}yn͖cvNzo1}ն+֮w9/;pŊ3пngΕfwW߇8l,;F>ȀWpP_j9ׂhԫs_=1X!>vuk8\o2ҰZ᷃8dwpaѮ_s쫫ݿ8mٹkpp$$ra9/ >^('ҭMpöQw/j6xb@wcs29?9' GB ix`RBiq'eW_N;n/5;&?h;Uqy𻛃jhuBd5<)r.+9`ŢK&&X][aww j/ //[:NMSvYX׍fg૛]Vz6gѾ*=gN[)*_$̪Y߂poO{{/tU?P2`޸&r_2*o: #[k+z69 /i? ʊ ͅ  ".nS V;$-s"To -NF9,ޭRmf.GX"ou-'vId9;zʰUn+Q"l%lvHk8̝| H]|~A[' }y݂Vs-{&t(B!onεuҜq$m0 vj}P]]}rzAۅlY!IAюI>wp {[Aw8=3Iy)G f:uB|Ol.B[О&]$gϑ>$q7q04Iu;^~!eYW,2Oo''~CB<*J$a%H&M4qdoMQlqTR/ zbP)WС;.fcNi:''r+d@AB;+FOʽS5Y'$PXXy~ZhHY ǜ {y􅉋sB1,i^VZ$]_14[M2x?z3/}o~VPLѯ Uyo_鹕5u~S{sJIAa7 C8G?3 ޿p[±\dm_{!`35!'s`޼~Л8w/;a<_g'Y-;=.g'bv8{2-9*6ԓi {eًpNp6oM$+ fv8;ήeiᬝΥpLZ8`g`?;|ZJ0;=^goL g׳ٛ7P퉔}I g~7-}@v8pA'R/%0AZ8piGe?N g$-}v?MKP?1;YZ8piS_Og9:~9-adN g&-}Av8۴pEש8l4;A˳?gξ:;SZ8psWoN%-}Sv8״p-ٷeO gߑ#-vLKP7;WZ8pig;:~5-aOdp6~&;!-\v8嵜֙<0)·vL<6[ ~ĝy] NgW|2!jFb+NJ_X2krR\5K- nXvz'}h`k+Օ7sHde6m Ֆf+=ɪ\`LσT|PQ6JHd fTv/valӵ|Qx(YaNuBPcO, ?D_6Ҭ=]|մO$I)ʕQj+) ؘ݈Ku!W-G$ 1}Q}̕]:Ju*FŮ|+jm,ݟb▲7IԄhʴϪJjuU BPdX:A]LP q٬TEA5΍iQ1,#w2X,AHZ*\Mp"G5sKLN$ +4X<[1kV+cvv7l>3Y(*{Amrfe4 8:h bmڰg,s(hB>iUiA<[4/>4N(*ZET$8\2n>'m [<.2ʙŒy3lJ93X6B =hj/J 0Eo|FX>7knc&( i 8LaffbRʊllfʖmhG8狀g6_$kdn2![XFV߇+Zxȍg bdXdl\5W 2Hd f/[F4+ĥ0+$% R2.ki!8d*ʵʜV7e mrn*/V=Qʆ|ͬ0=$@}w>2Ef R]ԜC$69;3\/T](y]i=Y 0|ɳQIAI,rzszcQD26n^$RsJdhSZjdQY%RX}8*W2v~>w{WOe5؟כ#Q, CRGUqKpRڛޑv&U]0-]>YWzo1+\cCQZUZivăL~k @[R1#4נE]4.8/VgȘj͜n%J~plZLA[eZ[M k"ƌ;rț5vC k;bɌMoyRњeQVnE!7M}-S&IUfU ECsVEuRȭE1"+Nz $#ӹʜ]+2,̮. uׄq~ܯM׏vPoo=SVojOF{lyoeFyM36ָu h2qO*.Щ ڭ|mctr2lY6< wKV`wKht*3mPyfc^]y킛5J /y=r0E@/\'[F!ٵxLH+i5;,tY(]\|V9+zX֝K!#T+W(ui+eԳ/HTH* U_o=jWRޜ&hv5oz6zZa zHAqChпkhޠ@ogV! ${"9W&Q177`A&$-eZ0ory"1?Z $2Xjz*ٔ\WRBZsYEqaQ,h/rͱJ9-˘5<{w7-<$:}×EiLJ^}P<`νŬ\,h,]؃Ck`LEṡh hzH7ǖV7˪K,luΞ-kZ^DCwfݾ(k؁Z* Zź|D6F3;3\y&kz[A%D;y(B>U\Vr3BsںΞúN@tu(( 1c̖q&cYS&Sq'SwׇTse SתOӲj!I"in0wU' axxw-28: 1"J6JV{XW[Vl3BK[0;76a KFQ3Q,C#W3xEZ-mL ٻW{?X){jE7/z&~ yQ&9*;!a_#C-kqewYZFDA3f-?EьwdسËQ2,N&`GI^.#t_rx 띏ё0b~hH7mV{ ތz{݀Mbn\ ,-hsaW&2-\ YpL|ͪ`ŒY,⼻wo f;miW8B ZFL[^˦!3Pły+ɂmDJ6m2Kz;|(Kc*e)%Z#PQ.r`-.V(ltVm0 ̌aqFo}^&%@x ;OV`A/:v~.W(ie{_2]q3"pJKvgv @[/7[/#rk- ն|S]t9I`@J3oC2 8fٹ4E /l:eh:2x$1ΫNc8pkIHmG \#˭R޻ofNr4a5+~rB"?AV]4gm7om_--K^! 77.$^a%s! F]{sp=Ij`NnvuD P^r~&=l=! r|gC“<#4}Ǧ~qG9]jٸtdރ+~ȴΕʰWZPX0TzN}x$ڭ&KTPGTyM#3h͏mѶ  bhz̰hŜPX8ae\lZ«OPvڍN6'qI,L;nǟMp!&S@/$8FhBDOS~quC+l~&mhFY2\j7:Y+~.wj^lSvINV[5Xu _r7"mqo5r eq )1?􍜥CK6a` J+|fΧ9$=oltndSVaA)t&Az ߥڭ7q[ ߣ[\*}+3 WXd5fҫwaS*WX!*?SJf`8kgJB ~`ox$".ҝ6&W;d\I\PO*3qY &ܯx@&g9ovż%@R s,=8=`eo4;p9D$R |8(y,5u-'_N9Tv+qaGwjxsVz(HTzVx>#2k$=wFO4 Zc9;vNgUGQXԺ_uςvf皼 &~(#nq@T@a݂ev̿+ٞr/hQ'a Xaqh]}%!lSkf_X&ƑPX>o޻fv}zPag__Z]s~TNXȮ^gY}ik]\y P ;:2weɒF Kg9KJ,7Vװa.Pa[)j ]Mָƒ7s< Jvd? ot#8bV`_Jr"AZIqaS+J~~ѡǡ‹Y ~F$ŝJ?tU+rs:Czǭ-$4Yvv`Z (,A+kRal4-J;YXQhS' x—K/{/OjuA'8yޡS@Z5Fɰ OtG6ǮD?IK>9| 3`aԗ6<$.h:yPXYG Cc6j ;;@*lugk{w'Yp ;-?0;6(YiQ'i5D(?A\ʃF{e~ <+1rqUy9A䗾XaMsE's=[]!э&wK:m Wl TPNŹ[sVzaULˎ)Es":Q 2L+JiS)[F~-P=\i5.1hPӷ\' k9Kb1z vp&X0+?\(M+qAf{ Q-[0.i[彑 \.]vjqʣ*(d 6Om']wPM1[+,xڸʫy)3l(^Κl뎨͈V.<oԊ4yRjieu(zhm˛|NCWV͢y|ejy]m|߾+ bo)\P*~%c//,`A^'-KE+@լe䪚%$؁٣dVe {`0` alm"wڣJ4GwSOպЍysSz7 5妦=BsںΞúN@tu((1c̖q&cYS&Sq'SIlAS  b+r'nG }$A^:MfZqhkUGn ܧ=ݙ FZ),O3&r|M챜A2׿ Rv\g+A͍oQ?WŽ+}&ZBh^{Fo^Ń8v4cfofo{2+xvCmLngN' z9z9RJt5{Fc3 1=06cQo%:zD6ݼ^JȭbRؐ8;{cfof,fYFW69ӎ$;zComma Separated Valuescsvtsvtext.csvfield_separator (?:[,;\t])record_separator(?:$\n?)xڵUQo0nұ"<0@XayuD,v8l$xΚi_}|1R#F;55[v| Q5쮲Qj{ƛ ̅v?S1+N!(XByBϰCmu43}εw]h)C5ꅲo(#q0ǂqGj 59ig &3ЀʎQF#D:2*c kt|coUP"F99I%RhOȖ86?Z@,J GۚQ Nw+Qg6 ;>ҪuQȨnkM喭qxCwODp9Y'?RV.bX%4*Eq(ګ= ">E}C(B7bC_: jKi;e'˟?N鍜N ]N~hBZAt&l1a.) ĔMf澢Y*P4i6rr*i* MIM&þ䩾 R_,c7MK҃Ϗr: KR￱b{Ex1dSaH9,%MEp@GCXdMExڃL+-VXJԩ,C=X2Hԉb;"Z.JtqLtŌãtD8DcZoy.nLJ=\MzC20rv`;? g /)مto*nוX8jJ A3W-0^9f ~Iw}`9>K;wލu \إ"aoz!n"8GC' ~T`"hRP:s^22sZ5[ek3 z . kJY=lE#PЦEvKH<]q?+6[d>81c,w.#R{e!Qi1,*/{E-FmZ[l*_ѐ|`ܓJKRKW+Hha^8+h`k$eGg!\bxy"Ҵ2͙ "icSm +#x'ciӫF}M{XØmj KL-յz`GZg%yL/=pK)a w~H]MDY"?n沦jC$fR"kL:n.s&_.+T~a ejcJQB#O:\Ӱ[/./NC2Gv󜗌,^h U"HJBo=[N^a0 nd,_)9n03 äӖnA*;!\eC^MDz%%~opwŒ05p!k+V"`1`XT@|'\L:m_&H7Bh , 2$=Siw*;X$(݂cbRZbȘHﶖx΀ 8Im0O]IR0w*G:~KVxOott#2MZjrƒN~S}֯*D{,];n:m/zIy{ zpqۆ[+)nz͂3qNwl?;;t2锇gpm'eJC]CpuInfocpuinfosource.cpuinfoxڅPK0mν]FƃVm֔V%:%u%K:3|1I/ôX?3d4S^zvypRupW>%/h\&8mcK,MMIPpNk{_l\ͬOvws7ڣ/Crontabtabcrontabcron.dsource.crontab cron_illegal_punctuation (?:\*{2,}|[/,?-]{2,}|,(?=[ \t])) day_of_month(?:[1-9]|[1-2]\d|3[01]) day_of_week[0-7]hour(?:1?\d|2[0-3])keywords?(?:reboot|yearly|annually|monthly|weekly|daily|midnight|hourly)minute[1-5]?\dmonth(?:[1-9]|1[0-2])words_day_of_week (?i:Sun|Mon|Tue|Wed|Thu|Fri|Sat) words_month4(?i:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)xYO8o:ƀ1ik/nHSiNh?4隂ƴ%л~vjkېƶRYϴF79fBܜL80J X l Lop@8vBPq{7z? 01M3* J'ft2)K4&Cl2 pg]e$!>dc| }( \2'x$`)C6b!тK*qJ-ĥ=䰢H+f^x>4 %s@U=l= Xct=C8xn$c Ni\x,Jl$>|L.,^ ]q *1.:=~{V5E ש)B90 x#cE<$P4d6R FYf>/y8jA໭ oua ߌ(ub<{rD-;zU#,dوj S(glS-8†Uѱ.> .~'N%kVYą\(P1DڶjR)M1''<+'BEХe_݌NvqF.v i(4'hG0z; Mar3*M$m|J?U:?5B?>ҋS7#H̽")$L_ujtm,(UeEN?չ.ҽ#/0jO~&=2qv?Fل3bfVcf?oh+o({$/EqdY3n筸{D6}B[9nt֢،8]EoaUP/ΩQAgy3?.g)3hnZζY/cQś^c0 ܾiEcQ,7ٍ1U)-=r#H"L +޸!-KVBCuzPa䮫xDR-f 6FkL ֫ 3*LǺ$h}Stz糖~?MLRQxע_ Uts lK4l7Os&G@`_t [yi}$ nb~B%Crystalcrsource.crystal^#!/.*\bcrystalx=vFxKq',KDl ś8^=CP HbXRTk7ۼ96g~bnPD$ERT7E[ \uCo)2Lu>bv MViZǏ"tdU`a̜<CO>HJN$]#@'S @ @7Й[mFq@̏1Vj*' @*&Qr%Q&Z j&Q.Z @*_$Q[jK>k*_%Q|Dr>Mʷ @*U2 @* @*UV]#NDDr9ZEM-a0TY> U :+]orb[Вgb+X nL 7S טꁔVRvpU& \ZlL2 j~J0DF hM>hW6=d[|K!n(T64;)4&ukɯ%x Uiˌ+VSjjK6 LAWg绌Ue$RLڈ>z?a"l? nK 1>7{Fx!00't2x?d:P2@<% ?Y?zDDݘ/g_?>A]t Dzz2}+j)w>Lz.L!Sv񋌼]Goڎl{|B$ bySy&Ï":DB 0yEQt;N6UBZ̖dXlS7Gx2M!CM=ꊞl{kV8C q+N$l8RaצDX7Q75ٲ&ԓȼ\TS[g!^ؗf"٤>372ĜCLH YǺO!_mz[|8+O|$l8RW>" jc|2Zm榃-+K4hu W*f~G7RV9X}u <6L? dw]۫ Q)q Q)x3 '4+])=5 "jƶ>=/ҡ$ID&S-BiHKyBz)R/OǯڄR^_/(hzּcOk%=w0dV-dVv|W2Q2%ݰE5+|1jhGM5M2Ќ^}gNGCE&5D4?z&tR%kǯ‚c4=)$j۹ӧv(.W䃰6jd-KW2n+O(4gQܗaߏHִDߏHrm9Uo]ͱ_٣=cEDkGIgjPg i:΅K|8Z¿-L6GQvKzٯ# qtGPw3&ovcv5ML+7 ˩:`Vd$U/ä>Rd\HgwLĭ <87͊=Y2mHkSI!L WOP,ݹ amMAպ^jjvm`')/E!e+q`OrCB:űa}|}Yb#`qAѕp+'-\݋K){=&:k5( G.n mT3qs4; Ҷ+o9}:Ѱ;V%H ؿ1s7GR㖠߭ Nj뜻7|[Tbc v)K~ok4 ۿ醹,A^mec7:?zV[."nI*jfmom>کJ-9׾{O\Gyq9\* dQ,*v6UDU}U Ҿ8q#iûmsm2i޵wr I>eh=sjvOsљrGKh9!w8 \<4>'%SHPel0>jZt8HL=cxf@bebofsǃp|hj#<ԇu|ie'~еa_?^/c1.#B8/Eh"HURdM%4k*LY03C21_]fl#h̏NGδeU7Ԋ %DTpPj \.0rRaOI !(f2f̞ 8[HJ$(TQTr KeTW$T8+TylnIUY%}"fDWՃ>3nWt3`ա̸υ3jeژ"F܀v v>>f᥏#,80㐞0f$ r޽7|w.*4At7Vd` 5ئ}l /#Q1ՙ8eܝx.YXienT?%$f ^jIrr=˜c 0[PVw&miKGFW&uk"z kPj~VˢUݠzEpn!gr]E΍HTWؾql؎4P܌B #.ES'E_D`s+Ѿ"~,lcl/n{P˱.N\?O^rqQ$y\'<.Irq"B4Bq&N_ ިq'PhШrKzor eK dRT~ ZSLU`"4hE%/)z]cy+u=Μ{nn1?fK04ŭ)`[PGkeǸ$Q)E=3Ϝڌ(&Y51.\Ce2Lj̆-R[JL+pV=Lz܍>&,ELODODᆋpEqEۯԦ<y\Lt8biC>Iei2/uϒyh2MQ^?8Eym&뽿Md$N&EOd~E'"?MDQ(l^to7p}vݻA@ #X3U(h:ʄk 0 "Qmo[ Up+[S$nf]bp)UKi}W<#T~^?^0H|`yY*A X8Q">*m\m/I{e iOWGjt_~5]SĂbt4E6@ě\ɅF?#ZH$+ITH$VHR]/${%n}#n}Sl*5> *R=| y P@2; T:%]Ed8I{JpS%)Q(&y?An XdQ4=ɘN%XS=yN0 -I* 0eRDҦBYBJRt*s^ )%|^?C3\}!OG4e#U+t7! jآA3˧#u5A2ig28@V;.bs\~CPȼ` "t@<Prɵ)wLdm16vUɒ("`bRڝTXd. pǂ!ؼCG􅎖)e47bw?RL6>W]bBlcDmP Jh^LKq}Q`R2yѼO=^U_!tbÙE\fwd-WV8O||Y+9rk9@WԌU&Si~T Lؚ5$ȾtϮV^U,t`L;nytpx$|x#7t=\YJ¨{{ s^MZ$ coϴjގkz0'Jjewߟ9OY6 ]{#KMJM~91X.z# J[FC̴qCmeP9lh|7j&7 QݶXi?] -632z2")*y}d-D I6lv,<_ZwA0Ѐ8vT']A?is_?8,2@> B  e՚QqE8ub1tֆ];2g`"gs*@tjC8"@8-7A`Ϋm\L?t' L1睊Zf9#9TzS'zA簭"ם`c8gǶʈzb4𹃇yMïm[` Y` 4hP48K']$B?&Bq8#ڲF4E2McD>! e <h?͈Z jC狜cAV@808<=9g9ܖ(oDS >8</(e;Xr ͸7v=F3uhsX- !(kju;G.eJгl3@xWjPΘ!]Yy{|}FICȀ+:dd@Də[ FpZVNxVm2`{e\h6C /$6h7CkkiS~.i$fۚz%Ҟ>=W' ډ"KݰSg4(9-IRx{0q;u=mY'PUw?QV-^<13egy44:}$zSu=̄NGjWC?IQg^t״R.zb!ϸC9TVDockerfile (with bash)source.dockerfile.bashembeddable_directive,{{onbuild_directive}}(?i:run|cmd|entrypoint)from_directive=(?i:(from))\s+[^\s:@]+(?:[:@](\S+))?(?:\s+(?i:(as))\s+(\S+))? identifier [[:alnum:]]+nononbuild_commands_directive(?i:maintainer)onbuild_commands_directiveh{{onbuild_directive}}(?i:add|arg|env|copy|expose|healthcheck|label|shell|stopsignal|user|volume|workdir)onbuild_directive(?i:(onbuild)\s+)?xڝVO08ĤŠfc-up[C+71Ďl"HR쐘$}s;c8T@%/Q5όfwi8$2׸WJ2;Ny2{4>$!Lrx!rZ# LAD Eɵ۷Q1 #+j9;[bjrͅ5{-Ϸuz ( #[V:( f㜏Kn1l[/D@ܐs"iL)/v*;v*4 k7prc8^2|lwV4 V3&cݘY^Ø1ohj2w}-muT,X`EK/ڑu Ġ0 j\/yw*u'h 5\w2,I K> ![;{qSRΚzٓK?}v0׳ێX4 WJN,Φ rvoNk Dockerfile Dockerfile dockerfile .Dockerfilesource.dockerfile)^\s*(?i:(from(?!\s+\S+\s+import)|arg))\s+copy_directive4({{onbuild_directive}}(?i:copy))(?:\s+--from=(\S+))?from_directive=(?i:(from))\s+[^\s:@]+(?:[:@](\S+))?(?:\s+(?i:(as))\s+(\S+))?nononbuild_commands_directive(?i:maintainer)onbuild_commands_directiveg{{onbuild_directive}}(?i:add|arg|env|expose|healthcheck|label|run|shell|stopsignal|user|volume|workdir)onbuild_directive(?i:(onbuild)\s+)?runtime_directive({{onbuild_directive}}(?i:cmd|entrypoint)&xVo0o{/F"T o,&4tTYj-S]䯻|zfC-\u};puQj0风5w w1@6 Trq%󯛢J$dQV(2nVRi*"I'%3)U>sމxO-4׏[jJDbT%,@9p[xdZ}gkH@䚔Ck3$9lJ6R&kNJKOMDotENV.env .env.dist .env.local .env.sample .env.example .env.template .env.test.env.test.local .env.testing.env.dev.env.development.env.development.local .env.prod.env.production.env.production.local.env.dusk.local .env.staging .env.default .env.defaults.envrc .flaskenvenv env.example env.sample env.template source.envxUo0nm1lfJ4yFUn:qB'SKU |rW#SN8 0ŰV\ #k:V;!NvN7%nj-I1+̰'hGJq[BPCQv%^cn@|V,-Ԃf0Wĩ?K8Y'='}+NFTEhdZ N @tA .4l-E ڧ1x鑾Olr{jUƠVƫ57uŔ4Y0*Qvyҹ ݗYp|Bmx/11Z1weoUijS*0f^U[,-ꑬMniekUUJmTdϰb? %XW8 ]Ne;wؼsP[*3Pm׳C b\ǀnq?CARHO __n;A 9 K9 yK*./10}XmP-A[6=k:hN`y=c950&oƭIn\^Uy2Vz/cژ3VmH=.]r\^SUeDw%k Jヸ`: \\R&<]vha(rܛA^""Doeo 1 Z E-+(%͇J0*[U}D֔`0M ӕ`[W銊mC>UQ FuURQJ0{J0-%}%%C%GJ0UDէJ0GJ0m%3%UY FuUQQ]U`TW5%c%hCBs%J0J0/` SQ]^ FuJ0?*tE[ GTW(UQ]I Fug%s%m_`TWUB} ^E7Um*cqIfC?^@׹Q4J$P"$ $!0#;qIKfg͸$>巍9G`~+1~*781j%eю  'f\cݶ;K:de?v򺸒{qJqI+AH@@mN~6y=EH$βЍK 8\a"r}ڋˋS'#jv~a=f\ox .eiږGݏ$pif˸$6<<'ؔ"U@uUT m˔;Jp;&q|l/WXI6Kw٩%|#|!gpzf)8Y%X|\VljUg55l(Qbն>4`PO 0Xu5m4 J ix(-YmRdQ/.2-$MP/>HI&{ /YC76wykF ى>RG _끖f:hQ{9b44rC) Qy(V3qa1V1|dN= ܶ)EvoKMOfs(ٖ[Ι]nM:hVe%ɏ8MkkkBuCI!Mvt寑QD° [n2k|Qq'7@M/TϨ/ Ӵ>Rw77LC-!`)bRk3 PPX(8RĤf@31 HUĤ3 Uf@INaJd̥!n/5б4:Q1]Ξe HYeD6W)}4NJg~U7tLdĉ`>/SۜĆ0qf=18nJwSo F47s/<`v$u}ba_" !L1lN ƹz.0(s<+\6;n@z$S[C MHG𥰕3 ~/&50+1YYEGU\(R V+Wu#/YLJ ,ir/q[\_ 5p ;#8'|8'|)|dܷXϛn]Pɸ,ɛ]Z\*\cf[N,XX>v]!*8~+}Lq0B#8IlH" V[*~dDN@r_vEOrGr)?$'CJznۆo]׃.D&;aBj[K؁Hў~W ?;tKTL ~;mύVCVkp_Zgsox&b4vA =ƟVT; }۳ѱ p~BmJ&&8},RгH /T q,XGsᦛJF}m}~@jAZF258 #hpfQm}ٳo_wϞ/z7 9 xif0()aNh.S!St%hUxu ^*Ce L\H01XNjR@y~8̊r vFTuUJoV26uG&IM %n cx:h@:5}0kD)O"+-/h <V#v_VVR! ?q۱?4"9)+nl&v!L>MP%x} M#090΁ fEn LPTFPW_->9M!1aiX4IY[仇eBj1BzwbZQɳ_br>H}A~Snv#QK$qC$en\FG3ɰ9YP@| G/=.*Mr;0֏}hF;I,m:A P-oq\E_8MBBjv^,wF1 y~cxG swk [ #BDgxU@JJ<*9F K(os:!]7LRRC8d"@tI-|fYvܷsfs&H:| -z;Lـ5(Q&Ļ `gDEeau;ݎk Bul?]q>&ra6a iJy8 ,43-j?-: iDY2 Mر`ӈ9#]Z 0 #!H"auXܻCR jTӪd#;n1>0Eo-79t+UbC =rPyyʘO+Lij%_: zeFԥWfD]W^}}&#<".ixpW:њVP{) n+$~#)""pl2~_WgE‡aR t ~hh"#KǷvtcVsCKfQЬ%SxUX\t!0cP n=mԋs[E>vQEx@+ᲕSq2} Zp*g !d"B㲰H hLʕt,>^\K, |ZLD}mOɋQвO)$}Wq v !hcE2Mơf)'f2u #L~7.#fWl.QZ9E{ߔȈ{VA]7,-vy'' SKCZ'`Noؠxx&;#P/kq^ܕM"p}dNQx ~HհRDv@gQ;rMiRH}>SdRfJ aܟfJj` OՉsr9 <0ј/F,?1}닩}uZ5b:MqmH(N`ϩl\egq.>1p .# 2y<ܵ HTML (EEx)html.eex html.leextext.html.elixirxڥUN0RڊpBBCXot41iE28—g_gQn|pҕ'Y=DVqUG̼'f3f g^3m#%z?̈́H Bш,*I 8\kMFlRKb97g\ /32> \П$ Di*IkKv!>ƶ4RQ /JsG ZgVc2i&:%#Mb{H:=#3wsd>1Ї{~a'n%{MX#JNO&TGO &Xn'A6d0%oV2|xJ wp}t;;1Xa+@۵԰WsX6ߡ;=pAg1ɬx'oYV`=&u=~/VΠWB2lױ] [Ly}b񤷙[ a[[r͔CI5l]J8 4Kюp|.MB9mzn 26~u= aBϗ] mDdDˆbL"Ho~;cY @FFm.)zy=2./lwK}N%JmӊJ|A[A/)#5}'^'ejC% t1k2iukPFz3MBm緾sۊI)#A0`6P&D;7 'Wr}oҫ0eimtOr"H=}`ʖc_~%5P>5lm춎ǏE(+b|_|$MXut*p[7h0KxzK(`bB&324e= StNF-"v^O1)ޔzdPu(V]!U4hau62~0qu5j> ÷"˞8r&FetCZZ :7٘@ӠKR(MPͻ:̱%q颤$^ɽ!YҰNU~,ㄎ;we})Jl]$ڹ>]d<O/UM[$u໔1?q<5؎Xwvd)߅JR*<2K)uK[JGubQhK\ᱥ`FXS5MWh$GZ !YO #h}ߒ:&LLJS[Z3廦)@,bzQ¸Ek^6YtcʰY*ݣ\xh,hhQP5Bils㧴f ?T\W a5DibS 4kst@e@:굒 ?AX5sa8ذ, :&baz+ ",calpRLIQ]cj_)B@`t*b | ״cSNڜ]Ql<=PO%J`'>:&,׎62ޙ/:q <2'اvF,r ˏz`c=ô~r!bO#g.E|I L󓈾TP>Gqޅ+`*3M%ʱv+KpJCi~4cEﳎ*EKYT?ơkA[vKd=E7Θr݈eF=~giY5=ǚjMڙ7b|IZ )N'vX M;F.H&}k.a `*!j8a(g4H0۸R"IYhmou^NQ>V81`\D̔5[9a4Jhm&/h ST4˓3H' ڂ-@,V=C _G*_߀A1`OmWh*RR%64S ]E:@a$%=?J QBi޹D >DҀ|zD=}x| xpo&;D5ϻ3dzE'] faYMN-BmL nf?:h=yQʒLJv-^`vMcyIjqr >GcŹ~q@2GU6W(K_6g~ Epk[ز*/r&Y$YCp]M~ZK4 *Kzif} +0lѕL]Y.+mp "dN OZyP\ႍE ̺HTSVoe4rpJlhZ}X2gv*ƦK_ck(Es(,؇X}ᅔtEt!.$ZopfF 1ӟ\OH ȼh,J+=I:-fa x̓%;k&|Vɻ<3l>j/SLF3 NUL=$)\&Juv!p2|bu-AfJ فN7'KL]KDw TXc"j]/[H{az|"|aMp]Qlks=VIfr|g96;yi.yK$A9 . 3C Bښ3uq-:&2t*GXle:.Y8B+ۜq1>=&9{.T,JU.vNTdvTmYߪ0!3B&Ipɢ;& )J`r}l((KqQq(zz[%zӊv~n=a{;؍n<=|@,Ѳ2IΕnURqrԺK^?GT;©e:qOHQpП3\4r!jL@KW;VϷP##v# ĭaU]E680U![q*4Q]w|2bCٔme4XٷX& DTŠ+QUB9.v$7Q`vRJuth,8oӠb\ ?(MfkU*R b0ɘ$e"h;5ܵ!Q \H9ՙ4tn X%eȔ]lR]䙖1ANCQ}#(9r,d2gZ!Elm Compile Messages$text.html.mediawiki.elm-build-output1xڵVKs0N0`v=So1nޤȲʅ_HGC~}z[sb0f̩l5c ӌ97gh:nM$%EQ#Wrꃦ~%fP>gX)ʂ^dd )7/B9[ MoSj8MSc)ϴjJM xD>$7/>i>QdxE R(h`.ykI8"aHDE" A-ilKj+@LhZ%$:pTD҈ƔQYm +ePt6ԹA?֪/w%fRsHB:VQ~؈Efܪfy[Ƕn_*pT7FS ym yA鏝mZ4oNBԯ])gtFUDkT)ErmH%!!>*ͧ[@}eqc6~~MG;huUZ!al(c ) XD!^])D aiкz^e_ a6 GSe/{FOΒq"zHDBEry wW](z"I&VD +'WYӎ(r?ߖ܂/GL2K9k(Ҁ1qpBl}Y)KS~G 0e &,]ZF:XOUrų2Ai(; 18vTM'L'olpoElm Documentation%text.html.mediawiki.elm-documentationxڥQ 5#~}E0lMvѿGu|is=皓7V[.<9rҝJ^ 9GHs6mY>skVkSJh ϑȧVEzpM~'I;d# )QK>F~i`M{Ɠ{KxMuElmelm source.elmxYs6O*xm@hIA6cGW6Rvۢ4s%ؙP `ymٖb ;|(i>}VsE.mڦy؃֝aNpݰb.)i19}F3+yhia442#z#xnQs~=&l\Ow<\ WpR;taVxݲC90))adTIB*K FrF`Q5l .tWMqА9% TΙT.ڛ}_x#3lK뢞aG& %Cy: `><T+7(Rᙴ{A tK혪y /t5#)V_9L&\LȒiCzF7A/Xӎ}=& gLO'Јm? "32&B@w2CgFfQ톷Mה>6w>7!Sapu=`==պ=k3i`aaaLD1neldu#:lgOp}w\g۸+^>M^OmolsB?Ǜ5z}z֞[:g\k*ިy/1=^Ao7#gT:$Qu< чH)f%XS6`Bj_<6y([˂pᳵB2(WZdǸt +Y éY>-벞B~;w͒aOo?§IKߙ((lG#J#]fx;]^N_RFw!qGKUB0/)5$ʊk;֫"X UAƢr`VE  UUR#3MBa UKH|5 @߸uUïWe`ڵqO>ú%tT!_p! i\|AldC5gꍱQ{EnG4'@vlz=0Md(+ 0KLmiO73),`P8hwxSYԟNTq|S7@;gtqbV蹣G9t9O97΅+-HH]Rp|e Q)*^襨p"l$ZKUY+X},bq]wNJcBC#9C&`cƘ ;ӭ8;#=`F)&&f쩦8jKG{kI&t/ˬ}Wm(BC=`.&)veSA)v {=gA6E.ݱ̀ס|Ufvvdz7L{8͋nq≤hL` Ưж A'2(v.Q{N|3y^u< _r|p]D$4'T`q&$0GcaH9~)!Ww5&E!H- I؏$Svr$<#P"q/ 49$lF5觑G՟ɝ몾o/3y T0,NU}^i9+1tz#6ؕ68[Ӵe[.\@vw +G"3OߧLLG~}Ӗ|# At.;M1(dF2$RS:x}&=?؛iv(]$o;ȱ۱RCY)R{ψ1\ӶbxǃD}FHFK˲9]՟'Gd$0Kz~I)c"ɯF"aʺ؏P=X2 lY|%%ʘ>)2,ڕ-t}ܵٶb$ DӒ]Iv2O )S@'ޕ (VeO _(~.SIE:T(O(+E ;x~I7er6 rѱi]b!4.M,CwbTPIzE JŤx1S<|ݫk2Wb*u]eK o՝XeY@>SF&V |ѯ辢1W8֊IZ R ۞ ,~U+HZn&')DTXzVtHL CCWe`S}U ~3LŲyY%!x&|q6#=a3998!N2 N&ܙDlщ5 3̵H)hpYX!AnVA@"iHc*χJ: Y$%"R$^iQO.Vٳ5!ˆ ոs<߆]b^#x?zD*^kci;gWb͕ODXP O5̀0YY AZ֎Ԋf%YNCF[v4i/luXssC9W9\Y}[VBt:hUUo^y3xQ5u6A}:Ιlm%Q8oNf|!)\<&K( X PEA\-vC.3890&8ݗal%볼٘4k|R^1ƇD,281{wb()m}#K(jJp -56,?>ms Ԃ-bGQ^(q\VXwrW]s[9;2J$"u,-XmUz~5#Sޡm?1]4N;. @=+<7x S+{#;jxz7yg&Uvp~z"o#<#>ʩny`@j+@.z^IX nOm|0t?#`9w ygY9C|'MvmUv1Ϣ} C2IAU6L؀_.2-/fb*l\C8!?\MR3YKM^g?޳mlC)jwnTMHby7fU[lh_ rCya*{+" 5/t@. gCXC@!vKNoǀᾮӧQ3~-.UN< `+Hpv[+R)uC=iܹ9b.H1n(Bpϣ dPb}QnJLb/ޕ!#% R}0Qa_"R_i%0%Xi3b| r#aFiDZrf(2+hdF䣜3嗑ӣs9s=%pfUL.\XRT oq@V텾o^VdP!CU!~QЭ" a$Pㇳn##p0 ,9Y*\WjE__R JI)ahfWo;Md[!vz!1rӇa/4X}5u!r AcTr]">?#C J-\*Humi;KhO18繚Gw"/ԃ[۲#Eɂ ?..mIhSާ ?-mx`i3%>ۂଔd4>Uy>&ⅬlW|͡_HWkuM݄b&l_nf}o#W>o`3$8jtZv%ŷR0$ռ-(p-qP 0p4 CAvaƷ:hk ZGl0UB{d珢;.qTӚ5#IׅԌ(h€䡏݄2}`]zOn"YBď\u~$uA+>07l?$ Oh6]l'2gC%3b-椨U=-m\Zvlq]4,BP!r~Chr砬m=]qikSo7е5FZ{*1#~%'|I>#`\Eh = }7>A>?B& *]T LݔЪlȱh m"#&'-)eI~$%UaF`\+I[KD$6kĚ*\Rz!bΧcLu)@ jo88 P ќ*'n4,y]Ȩ=J2✜*F9}icµJQKtjUNC Lo=E6r)`AՌZybX،qz2e+fJ1Rl4Z)'3{䜧_%2h@5| .jj4.E6v\ДKb\:T儁isa_J_ˢ;J8 _ Wu^X|Vcc"rCg-/<4xƜm?1O8WQEA,) |B^&z>UZ5w8`HBI4֥i ;<~>r8ͤ8AOd3[^T9ڊ ­(@>aӘSL#30[dscHࢀj[[eCqX2A}bl6) ҍ1AD\\#&%R4$&oka ܷhZI!(ʿW((AQPQ(fh yI8s3-hB;`vVŃ\[#r'uη;~ fH'I ̅@0XLrb\ƀ)v b/ų?)t1x:Q'c2݂iUzϴw҇叓8Eytj6V&9 [HT~[&mZMaf`Ll}ύR 0.HF͡ Iph-6XT8H]8mP|L}9 s@$Vg>LF{tR:40qf6p ͈\4ҡhb\pg$e3ntd }iM'I_,|,`,l_8 *rs6-.t߾:{4rṆ MМY2Sh\av%LdSv*Nb4bŒ j03dDT ~́ (nWI0 rO#؍(Nڜ=©= +Ǹr'جC~ck 5˕#erTe,n{kf% uK 1'!c!S 806 Ds4;Ep2q,\~M::'QLZ>eX@xR'&1/N|b2l)ep]C 통o|z=kg7lgt3'rgtZL3Tz"ķ mX&;]ml0h5ak5`=kn,W ^,!з{)Ҫ M(^x`3JP ɣB(1*N"9BuL@H`I[[`mn!_E=PJ|kԿH# U~/)m*|'ZK[]o!P74M'8$:P<')<Xێh :80k/XD{}^oK˜EC{Hu*aqۄj]&{v !xߠ.9m@I>j$"iFZ:C\U$o,+357"3u! S{Ք0GeR2f{ Ew\SVc+uE8ˏgE$|Rf?DES>\g!_(dCANPgu[3P5Ewgt!'ß9F/Pap!1 b \K6ˍ'm=M7uR.y|yƦgL*14z$3s4L8pΌsͺӏj-r_U.O5LֿRWzazPbXV; \Q^-pR0~fd&qp#XuTl4pa}!]qYo9Fishfishsource.shell.fish^#!.*\b(fish)\b _redir_base%(?:[0-9]+)?(?:[{{redir_s}}]|>>)|\^\^?id_var\wint [+-]?[0-9]+nl_s\n\)nl_w {{nl_s}}#op_s;&op_w[{{nl_w}}]|;|&(?![|>]) op_w_pipe{{op_w}}|{{pipe_w}}op_w_pipe_redir{{op_w}}|{{pipe_w}}|{{redir_w}} op_w_redir{{op_w}}|{{redir_w}} param_sep/{{ws_sep}}{{nl_s}}{{op_s}}{{pipe_s}}{{redir_s}}pipe_s\|pipe_w (?:{{_redir_base}}|&)?{{pipe_s}}real)(?:{{int}}\.?[0-9]*|[+-]?[0-9]*\.?[0-9]+)redir_s<>redir_w{{_redir_base}}|&>>?ws_sep[^\n\S]x=k+(`[ l Q4mZLeymoWʮ^6_4ISWҤG^i?~ǼٝY~ +ͼy9?Z9=Gv۾l//7yhtӾe9m{ٴ7ιpjpSn0z-F$`CP) p슞gn}m8\ݼ[ ewMn* =4}B {H={>,#NHU< 8W|JTzB2()ePaePePQeP{ X6c|Z bxF$ TfX gL2$Pj @@Yƀ@ʜ۵+] &P!cj=o۝R熣-6fZJF sQ\4c:DVzY\G,qId,Yi2>KI_ͨT&WhT\}ϳrAcaXꘁOXbLad\} rd\} >n._Jx z9o9fh$zkow=~EXUE@9_W.}Co*Bw]bo)B"r$m}j6m;"-,}'o T/1NЂ 0rvԣgAb:,:,P@:,j.z9!aMUH!+ߊxWla'jp@jp;jpe(y8 .f1׭c5Z˝(z!~=zՍ`}5z_Q&'zɉhiI|W_?(7+,zK1ϙFrƐY2i=J߈8$p7Z'C/'^%V 0~= kދ5zauīsz;z!d튙Uze<3 vARW=3*UNJG\oa\wF3D?EuFFϒ$n1SIk~tV\mi IOddw;R S*/,X.x erJՙZmְuOyhxWPo+g*cF0ܱacF -VeW"j0Hb}w3@aS, F޴W,UkCjr]+{Ƭ1[yU]ŻvMjۙ;fԕZI6fd^/|P+X0@qW, v30e'+ 0}A [yq*qwudhtÁGZ։)`Am r 'DВ!ٓ5L٥ YI;E0 [R~h(M 3t,n=hB9obOMv[~FKX6_Ҵofj' x4 (jȔB;l5ۭH p4 H)3:]gH/sN_Eor̹"[j/}q壾ta:m%zD$/(\W?v 3q$9v]_Ȏ \K$OjOZ <$T\e\DK! 63)=;rhSaƴ sW3RfCI5pWnsiޘtḲÖǩ`xRu 07 3%T/"qXyS#AUh4PRRvq;0,,olrH Վj_"N0`Amiӵy<D9bPǼnjɤƘoOM(<5W+)M֡`m,  k&J0}O~BL-ǥo|@Ktv]Qi$H&j#򠃹|ABHؘXDeH<ս n$zu rߏ>l'/ {6'vtN~s6 4m<2Ԝr],lU}nV m~"od<#/3é>VDJ\@Ch7$}:iMQ lJ[QN$&19IrpqS!Q"RX&kys+?*t*uj2>0uӌЛŻZnq2noeS.dAJ%a"s!t-N` ߵx;~@}⥂]\SW;1 Jv|wM[Aize_Ve۵c, b%?<wێZx2o.Xv0wb9 _V;׫$>rgֹdԸo-_u qO$,v+vpt>~bCn$ hx\(mv sӎLbIM 3o2h%Xj(RuX.HA眐 "e'Ջ08|xn62>*FbΘȎ8 {L[.`ǂrj⋕6׫Z-^|l6HeZ u yT`x^lͬ k?GuY_:d @nǐia] r`QDHNp˨mD:Fjx\>. ,@׃g|ILN>c3,VNC !\P`* 㴛q7G]qDLJcLVv7|,f_U R+ %@\(X+k5*1/.Åe5)%n9NRAb2UC19\.,Ȼe3Ro+ˆktm *JV6^ujD-n, Qz(%_S=33ČHGgY\3P$S,e!*|FX;3"W>m 4Ό2MlR{ cݏD0"2suz4UK% -u$>7pk&OrqMn} y`"zX0\;Z.Ȑ͑L~Sy j2FX /j<Mִl‚OZ s A}yMɊ7\ △CE wSsMÞ%BQ!T 'Bzn\t($%'MVO9sEyN{dC<~b>d偐 ɑj/ϔf=7g:zp2Ԝ$OfNnH^>"@ȆFE9wN}q.s`Q3SKpՙVre,VıӭsvHNw;`]ZH0) nsp~OH~gVVJD\YW>JϷ)TlT)!#я|6=*X U8. ]ynlSvUbY"Awm ÑHØ]w9 AgC0?K{ARٽkFsB_?@ 1kz9qKO(7?q#ĕJq 7II# ioʩ ,aFٿ&Ëȹ3RN6"Q{vпߓef|hcW5AJЏP׳;j9g)0$aV]l*,/24f';#9HJLjߋ$T z{9J{FdE"4ڙ"sN˧IIxaWf)@fI!gVo3 +PowwWu.)R sC3e.ʏ }Cͫ("9eN+wuBX C1?Srʺ?rx!⛽aJ9U%}j27Y:5 ;[84y4} )V5 o.$jE)]-`?1ʊ5ւ)ֳufV9Xu7w Z?"l#BMD!>֗ccD|`4=^ i$59eup^6E"_bP&2̦KyLNL>T.>k)rf`k8#u)DRhIn'A;ұ̽&j1B}Cl"u[K5^2c~IH9_H[2%Mo?AYO|2J9t(W1aG䚱!'ǡ 9Q0S/?r<5|Muo֋5LE5 } 9cƘ? =L4 ^EK36=OdDZpMY>^Y1,79{{j̙ ӽu`cRym: _ș^ϯutKu>?kSg?χЕ./ҥfe3E;K3-fc]bMJJƚ?dG4 șęΙ~ylATg.S[@\KR&!?[(Fortran (Fixed Form)fFf77F77forFORfppFPPsource.fixedform-fortranK xms۶Yvm횦IuI9l]}q|2A$$ ҡ_(EUvN)x^W}Wh4G$-z[VЯTj ƨ@ö5>9N;pS#ƴ'9IτsT4ٟQRxiGEo1 PMvBT8i9ͯQ5c(hxY{a|^d:9Q^)53rs9W;M&)"P֦Su5LuֹwsfNf~~ǁ"q}p6D:/*LeB6J}q(> ^5MKa6,L0yj]` C)Ο-yAZi8(ni\.Lَ5dO4"AI0wNXc[ fpz݆ ,!)j4<&vZ͞yO--4e8"UjDaABqΞ{@U  \Ne0A4A"rPss|ɓJN >Ȕ:_^啠3WB׹OMm.)A_Q !j4S|^@+" ̟ɫ&h,Vw1BF,DjGڈm&Um V* v@Ȍ pLJbAx^dճQ̢li_DpŽ +bjHi!qntݾxN |^+)D=0AcI>ux*gR='J'P9Wي&h)6t3lSZM2(.l(0%B N#8+%#g<1;|=QB!wmu\ T )uN D( X@׈}lZ ^UݷA ~!ݕ+8 \=Zoc}LuDȪmEUZ\ ]$@r,r2{r#qtOWqP *! 30UoVK5b5Fr{յ՘IܻڴؑvX?bd*9E--"ChsqXU4VsVQZpNY|\ZpcOڄa3fߝ$Dbc[d%U~aTDlol汄Zb@dݼuqq6DZaЦb);lŠiJYadWX~ K%P#Z:N|I{Ȭvlپ>A2VY<ٶ<&qx߫8չ<},oyUQ ҶF]+an?,~'uVp?8:b3띡K y} Fortran (Modern)f90F90f95F95f03F03f08F08source.modern-fortran attribute(?xi) (?: allocatable | pointer | target | equivalence | parameter | external | intrinsic | save | optional | contiguous | private | public | protected ) char_lengthF(?xi) (?: {{int_literal_constant}} | \( {{type_param_value}} \) ) char_selector(?xi) (?: \( \s* (?: LEN \s* = \s* {{type_param_value}} \s* , \s* KIND \s* = \s* {{int_constant_expr}} | {{type_param_value}} \s* , \s* (?:KIND \s* =)? \s* {{int_constant_expr}} | KIND \s* = \s* {{int_constant_expr}} \s* (?: , \s* LEN \s* = \s* {{type_param_value}})? | (?:LEN \s* = \s* )? {{type_param_value}} ) \s* \) | \s* \* \s* {{char_length}} \s* ,? \s* ) construct_keyword<(?xi:associate|block(?!\s+data\b)|do|forall|if|select|where)construct_name(?xi:{{ident}} \s* (?=:))declaration_type_spec_part1(?xi) (?: {{intrinsic_type_spec}} | type \s* \( \s* {{intrinsic_type_spec}} \s* \) | type \s* \( \s* {{derived_type_spec}} \s* \) | class \s* \( \s* {{derived_type_spec}} \s* \) | class \s* \( \s* \* \s* \) ) derived_type_spec(?xi) (?: {{ident}} ) eol_comment (?xi:!.*) escapeseq(?xi:\\ [nrtbfv0''"x\\] ) formatdescr4(?xi:(\d+)? (/|pe|[aeigfxp]) (\d+)? \.? (\d+)? )hexadecimal_digit(?xi:[0-9a-f])hexadecimal_number(?xi:{{hexadecimal_digit}}+)hexadecimal_string)(?xi:('|") {{hexadecimal_number}}+ ('|"))ident[A-Za-z_][A-Za-z_0-9]*int_constant_expr (?xi:\d+|\w+)int_literal_constant (?xi:\d+)intrinsic_type_spec(?xi) (?: \bCHARACTER\b \s* {{char_selector}} | \b(?:COMPLEX|INTEGER|LOGICAL|REAL)\b (?:\s* {{kind_selector}})? | \b DOUBLE \s+ (?:COMPLEX|PRECISION) \b ) kind_selector(?xi) (?: \s* (?: \( (?:\s* kind \s* = \s* )? {{int_constant_expr}} \) | \* \s* {{int_literal_constant}} ) \s* ) modifier,(?xi:elemental|pure|impure|recursive|module) octal_digit (?xi:[0-8]) octal_number(?xi:{{octal_digit}}+) octal_string#(?xi:('|") {{octal_number}}+ ('|")) program_unitS(?xi:block\s+data|function|module|program|subroutine|submodule|type|interface|type)specification_expression(?xi:TODO_NOT_IMPLEMENTED|\d+) stmt_label(?xi:\b \d+ \b)type_param_valueN(?xi:) (?: {{specification_expression}} | {{ident}} # hack | \* | : ) valid_range_symbol$(?xi:[abcdefghijklmnopqrstuvwxyz_$])?!x=]6r/%߷oEiePi%ϷzO׶β䓥;51Ù!G$GSI@Ryk<$uRʬJC4Fw<^oe'ž~w{'!tIH|Iq%^iG$NLN'qEc3dZcϐ*tbLXFn&i`iGS(;'`~Zyx^ %-z:<^nJ#p*;~~}M@=,"N cCE. @:7Ud$Mt9SEVXFmdo) )Re8S.R–*ٟAm < +ޥ▕*xtTT!ۊs# JgX̾*dPm"*[;{&eU $ ~t@VhgBE+'Mc?M=p "Q5sU.xc" /rוN:&ӯL%wSy2a1ߪqj]5=E.z{iVȺQP E"IbJS "kM%|b|V,G2;rfŵ8T<ސ FV&-H.UMhIq]Z{zoݴ}:Dt´Ci.G/{+M;_+PV7h6mr틶eS:"8{v8_1T*9 yQ<0`( ˦Vz06R)]'յɪ<C/hJQ𴉘-]J* _g/oFA1>hwxY%!N4ڂNj2007"5y>\?eY{nܼuXk >,L&O e],}ݏܽ߻>ߺs룬;XOeYkځ)@t/)֝jЬ #ZYPziU^`BurhءOVBW2@nw&&0}]ֽOnݼq;u㶍B}s4HX#2"h/ dAR>$Z_ۺy[w@};⭲XHXHRڮz)z$-!o,ٟfX;#We_ZD%iPy0t֨ܨQ~ f:N[n*9:Ao\V-ljX&-E,V˪ DHU\e}Wc +=F}|YYsK1DPS/V;if\>Bpeod( 6 痞 cMZRuk,\\ OD6222QWW .Qqhn6n90_g9ұC7Ez l|#-N-#=+ qmÊÿxkKm "YI ^FܲY2G3{{kc~-eÏ(AdpFsIZqSm13.Fgĺ$`U{\T[BFIuQۺN"8YjLӜ*'L }Uլ -cCWsl11z-U`$y VHi3v͑]sCYԹ~t;_XvSs^n֒oNn u8g"e$`OQR&|V@p-zЯ՞}|( 1x'=#eS S!+F4[ީ Dǘ(BAep#$#ڤ4#fM'쇮MYyôdco &bQzd1Ҁھ{o: (jng>vGI"ݮ9rk0,|z}[K\ZtK;^M~4DvƶNc3HO+-N'153\d?IUWuҶU\V-s輪d)$Y&xze窶0f4ᔏI (X!{5\;]z*F5ek=E$f=5$)]&d7b6Y.j$UHgxU!2T+Zmlz飵.jږ{BP704kX҆WpW O+܌NeMEuZzKVk\,'M3 ~<|^U/ª/Rb!%R|aZMth)FG .3r s1D-b3W@%t18dR\vPHVQ+Yԭg鎅.Xg %("_ee &[SФKڰ+P.؞*Ro:Jjݱ~Oá5zVAGV4boYc[Nh `Nzy&kMvi\3UsU).RLJ^jP-"0XOpo2T쎧vW8{T^@dO Ti0C5Gq k"N Q^/"99mΈu"@*6Օ􊀖 ; ";y*~s=='/ȿ]}Exkvwu[\QI9rI 1O0R΋Yw_jws71BĒ2DؑgV/T sM9.v0~$Ȩ'dWSjKne1Fĕx myo,^LGa(7F!nQ+An>.g32rCVtNu?jHQȡ = >Ov)taL 9R557qL̄BǡךW:L9:/tx0`dv|0\ 7oYG#KvTѓ5%)n;<mOoy}j݅ Tc똂ϩuP'_&!ב;5| `6==!DQO+G.6PU(Fu 1:0y~vx bkB1oR8-E9e֕n.X_*&m+-AVU? ,/`òc%]۵:: )f,%}uYZxK 9.-JChQ0'{oJ^6nea2uS|atT5xk3ӈ,ֵlЋ'D@FL}^6n\ّ)#%&?u95珆{m[86ɿ-F čIiϤF;jYs3a|hPޯSd[̕8YE=a{lJǭ~TęB!|6 N懏~" a=v$ɤK.y*; ܊bʼYfleS?'jyL I,4BTJ#@t;xzPfW|-# :kɯu3Ǭf.j,>Ydֹ-ʥBW0 nS5/nڶdQ:vӁ"dkj{%,=ܶWmFGֻiY<ז>S^9M$XNiHi]#Zʕ}˄m*y _;n#4וq oKp$R06hKWqCsF 7Zӑ1CԐ_;H)(NQ{FV}CP%,wa. n`Wv?CE&+wr?3H&0wzr ȣtt5Re r+'yUs2ײպ\ ۭ8L#XzU/Pg:FTߍyOk3ɸzim7G/8n4({xE F>Z/׍ }8D"8e2~ֆwBDxC3kK+Emʗx.tkR!%#*w1hO1Ϟ;x_q|T5逖Q ܰU"!R9cCێzI2.|qDUee>&GWϐ~ߐGCCW!MEntkg7b_Jx2Y7 yO,Nd(iltS~wDWvC| _C /7q׀昪RMboGո>ތTf]DK5qXƚ/:ߪ\!"5Q%7JAnd.WdJ>vVhWHfLK &q4>6KiU1 O|qBJJi[%R˃MW60wTaҞj^%v"rq%q3m3SmP ӓT³UknQSHA1e}Kokғu;ZUY c#V8|5tU Grj}ӟj[*ՠa|c?VtNgv8k`^|.I=+`^@;dsq'#b"iDZ+.BJ,ij&m\wnއfd_>ojqg0\,y :5u|좺KU=~d;ۅ^3bZ ) )kZi HE}FfAqx[X pfW3M/~v7xm)i6يD!g(op`k>bЗpY1N/U,I0G!}ǐ~Ő'cUq Q9W~?2MKQBtLWu\qWqȏhIbBԵq>rn."Vk)k[I75Jw":j&;ȲRJM0nMt?~UF~8(% 9-JԔW5LYCwO,ʽ8fS& uobK2JB7 @#gt݀L!)fHFoۍc7 ^kszFortran Namelistnamelistsource.fortran-namelistident[A-Za-z_][A-Za-z_0-9]*xڝU_0N}S[{Cv~>0NL!p}F ]ofPJ)y^h+ e sHmVu%# d|Fdʈ2Ό67UKӪ0MQGsbL0DڟiqxKgĞ>v6 r;iTE{-VXda _oϼqW8&_,/@Ty PHA9o?馗@::Α  )4"~bԝpl fvA$yy)qeCaMT7^]cQI޲q&A;&ָaz뙷~4gGZdzh͸V3e8ͤѭ;BإOpenMP (Fortran) source.openmpident[A-Za-z_][A-Za-z_0-9]*openmp_comment_start(\!|^[Cc])\$omp\bopenmp_reduction_identifierA(?xi:(\w+|\+|-|\*|.and.|.or.|.eqv.|.negv.|max|min|iand|ior|ieor))openmp_schedule_kind*(?xi:(static|dynamic|guided|auto|runtime))xZmsܴORJiKZ&vһ` 3L CB)&~8l_zaeX%$K04/jZڕh&{.ֶ}y;'JX,mӖ:G (5s19P1M%zӏPN-m4G3(yp'9I>rc|K mo+=|"= 'b Dm?PA^/nOP4+2؏Bwh'ܠMpRMbgy(g~T=^u.P ^mͨٙ:KO;Py% rQ=g?>}tSUaffmٳli"k;(HPcg&KR&\sS!4'q炧&NA_aW&O"ّagCu|H3_ƨA7L89,ǙN0@.B7W tܓ-{c%AGǛCWe"&wZ7d)9cU:]l8KX/vU4gloI[RڵAiU^VD+Ws[3h685Gބ>٤7]Z3lLdiw-մzZ#~=ڇwC((,WЧ>pYIÆˬAV$xCQ!}&2z`qiMhF`)B&ced!$a,W$a2/K4/ve2Jyg`FVrg<Dei8p|h$a `})(pޞIxm} r<=FCaN0lYBuPYbiP.yIoFf Q IEI yD^˶]i75w4K%sUCk4y J.j5*bXWsiljE6sd sST&kp4CްCY3H S,ܨ~__媥e$KYW[WpR]3Lr?$ܨ8 % ,Wཐ7׬_//6@$-{}KpW#&OfwiJ'\肣/MjqD24Unx"/JbCDAZ>$.܉!xA"jAXΤWԪH&ܬ)k;= @@?BcD.Pɒ$ʐOy(f,tPō^hG.)&uiԍՋWc0 `'\ ٢.j{{(3,j;J}(ddDOTjkF*Wv@Z뢑O6V4vOrEH7gh-xa[vSm6~͝i=ouޭNHzi|}Xd-u{KYGTy{nH;Cc?}i7_}(?yP/7SVhN}3‰azILkP!V.7s'"->/J.}7'#N^`}]$ R}M*t7G`&ħD4= nik?W5RO2KY>5Mf\Zp*_{$s{<}5 8 bY)Ĝ~Τ;aU^9Zf8^1!N Pg?"52{7%YIOJSX,K6Sٽ:SiRyob x}vfstabfstabcrypttabmtab source.fstabxxW[o0NJ["i{&gc%2(rYK,v"π.&iU/NJ;o:R. Ĥ-OWǾb3uXMDJۖ'R)8Z.NҹoK*}S'OJи1b(),^BsS+=)O =rL SJG;a}+IV҇mYMRͳ ܽ| iDpFay42WglISbLP9Xܶs}7e*`1;~&f|<z3K%_K$9\%QY?4[ lh|7 zh݀\۪ CZOY:p6žIY7ze:Gp,BM(X/_͵)8/ Ie8rN@sp񶒚kz~Hu^+ax# &vU'棃n@4d<wNK TO'}L-:Mjq)Nk? )Ń^}Z#Sst*(l=$-s<-Dʮ gy>Զk{k\t1i^nBGLSLvsfsgsvshfshgshvshaderfshadergshadervertfraggeomtesctesecompglslmeshtaskrgenrintrahitrchitrmissrcall source.glsl-[*]-( Mode:)? GLSL -[*]- basic_typesV(?x) # ignore whitespace in this regex \b( void | bool | int | uint | float | double | vec[2-4] | dvec[2-4] | bvec[2-4] | ivec[2-4] | uvec[2-4] | mat[2-4] | mat2x2 | mat2x3 | mat2x4 | mat3x2 | mat3x3 | mat3x4 | mat4x2 | mat4x3 | mat4x4 | dmat2 | dmat3 | dmat4 | dmat2x2 | dmat2x3 | dmat2x4 | dmat3x2 | dmat3x3 | dmat3x4 | dmat4x2 | dmat4x3 | dmat4x4 | sampler[1-3]D | image[1-3]D | samplerCube | imageCube | sampler2DRect | image2DRect | sampler[12]DArray | image[12]DArray | samplerBuffer | imageBuffer | sampler2DMS | image2DMS | sampler2DMSArray | image2DMSArray | samplerCubeArray | imageCubeArray | sampler[12]DShadow | sampler2DRectShadow | sampler[12]DArrayShadow | samplerCubeShadow | samplerCubeArrayShadow | isampler[1-3]D | iimage[1-3]D | isamplerCube | iimageCube | isampler2DRect | iimage2DRect | isampler[12]DArray | iimage[12]DArray | isamplerBuffer | iimageBuffer | isampler2DMS | iimage2DMS | isampler2DMSArray | iimage2DMSArray | isamplerCubeArray | iimageCubeArray | atomic_uint | usampler[1-3]D | uimage[1-3]D | usamplerCube | uimageCube | usampler2DRect | uimage2DRect | usampler[12]DArray | uimage[12]DArray | usamplerBuffer | uimageBuffer | usampler2DMS | uimage2DMS | usampler2DMSArray | uimage2DMSArray | usamplerCubeArray | uimageCubeArray )\b before_tagstructcontrol_keywords(?x) # ignore whitespace in this regex break | case | continue | default | discard | do | else | for | if | return | switch | while identifier\b[[:alpha:]_][[:alnum:]_]*\b modifiers`(?x) # ignore whitespace in this regex \b( layout | attribute | centroid | sampler | patch | const | flat | in | inout | invariant | noperspective | out | smooth | uniform | varying | buffer | shared | coherent | readonly | writeonly | volatile | restrict )\b non_func_keywordsif|for|switch|whilereserved_keyword_for_future_use.(?x) # ignore whitespace in this regex \b(?: common | partition | active | asm | class | union | enum | typedef | template | this | resource | goto | inline | noinline | public | static | extern | external | interface | long | short | half | fixed | unsigned | superp | input | output | hvec2 | hvec3 | hvec4 | fvec2 | fvec3 | fvec4 | sampler3DRect | filter | sizeof | cast | namespace | using )\b x=icFc K`!!'͕ `{7 `f BB096hz i(>[R[-anU]]]U]U}y-.{7]8t;A7=_,3@.ꙑ氳R7 yDw fO뚡B?GAJ];1X|CЋ\5,qrW*S^͞srFϞJ M0Lͨ%)'id:(RPRvo T0&LYGC߳Px~L%dyu=Յ{,Pwn`w>ʞe^Y$C"PE^:|YYQ A z6gyj%(VP%V K~ 6 LywRLSMrj0&!oc"m`L sٳQAbK% Ц my rK„h.N6RWD$ڇ㕧 W*zby-cq|-p$ְ }iO1H;mm LۅWlk W¦Eh{&O!l̟? 幘|#GB.$O$2$!3`ʋ# o478 oo('VhVrKL0ZB| /_c醶!(_z((%NK鶠ikBHs*^㙁5{BoXC p-g]R˞;ز4F1}AG =l : υJ!CCL,$p7!A)n$nu9vϵD !~^C]VEׂ-{MV 8Sn"GNC ;&)#6j|4/[D[0|b̞e XV8}(:>2(a[(c̋|~~_ޫ?IPɞd% :S! N er(eϋ*<̞b ~F:ãSA@ c)(fs%EMY"NI"Įd1=1i}!1}0y,(4mT<UEvpW'*&*&6g;-yNxjJ zF Ɨ3 eڲ+זlXh,JadDN=]j_vhʉ¡kENdB3,- •2 y.5^8f Qo _0. c]nR JM^IуԪ0N]>#k금6SEyl>?B55^"^}/&e.hxB%()~Vּ*vjRФDϕ6v44+H4:~T#U'J%i4O76S7߰,!5/;+e]Y?ЩT U2UAU}Zvf">jJ4A V ѷF|9n!Uj(:q\H0lC6)|kfEq'B[^zMxǚf]ss#V1 @);eʥJ_,ON2tF?[$uL˻m x39 szH%T2E{V;:g0K_:{dRX_U)Zex]U*Z!?O!tGO RԵCE}xz 2+ h\=-u uFLzeaơJNTũi丁ơNC)C\\;N/U)g;_MSZUP7ZDZ7 eLJ'L6ndMק{ -1(g` J;XQRJrcvρK3 CJ9"*lZ8MCǹzkK]YZfș#HDj"璄5]S&e2/)maOlf*p*٩Θ4* :0/Zֱͭ4Z_.+@ X>kvCj8v %/_\GB^+ayy`'Ntk^bpA'sg)'L̩Ƿ>&{72h +Wˣ =c|4m(ݹp|G{'ڝU/ss̽ێ=|Q0϶uEu-CZXάOuA`EuVamg`b7*&F߂#@a~.~{B>U>V al1O%E""sEE""#TG8 X jqVm{L (5LB,mbAٛH6څ u$>JGY^j$t%QDZ,ANS9\t(F왴2scG={uiCW0^d-1 Ty68g[ bRZK.*;ۀq>HRm_U]c8tך0SD]|EV+iF/’c%:Od=c<$2Оa ߷؜FY 7hsC*{pe3KGp^NoFU}ttj:v2-:` Mwu{<l'0s9*袾`{B58 ةz`hoQrxh KAƮ ۴L l?P O8xAI7h#Z2N>^ 6 Ƶ&V?c`o׍(.8_kcQWsrS om^[)^%. "@Ӓ wJ{ BȑU=Lm(M,@.&% ElKrlkbZRĶ,v;bF PD`ݎ.r~6Fq~Al{ᘄh羢&n1;LRȜCz"1* OC14/*WD̹kLE_k={,/%u`V"h&}kBq:("3$YF}D;*FLBE81w8>d䕯v%194)WQOƓU(_P_1+4b(q*FV'%V7!N]wHsuw7>ZG-1݀w%jcz<`v;nFOa{oftx1=:if&.%H5:tDG_Jd6Fn>nޕxAf3N'3I1T'Ya->0m#7_1yMybY>|Nr]Ϧ2UUa^q}ڸMI.vw^'5[Ja7q[G^F+l͸*LӚv#_ĨqK8ymNYVm)-$ԓfķ| %^h083^/S.'a8]g-T;ߓ-}8L'7V' FasAXPv$ 404ڜmoanw1߸7Xxv_?\Eצf~W}.z:F`џΩXcLIL*T>ocz"DjixJ͏4s OҶOwZFAVeQq^msܚ.9US>܋:6Nj9/Yus"ȷK#*pI,+=E~(M~WXg?֊z_-0\'u%t~nSᒩ2@/kBsTt^a`tTqؤH8^TXeuoO*pzB+)ǙrF}V_>mw|gKZ*?5OpV&W(l[+^ѧWWx08ڽQQm_Mb "+E)iYz6Zx!*S\CX qT%nҌ5T 9Q]}ϯċwEi9&ǑE;칅>SH,?JqoOݙ4UAȢ(l !#xg|eF!gµYʬt/ئXRb*#ME~iz*FtLW;{I0'k#kc)?;!m HHPH.-e6'Yel[aVX %e权[R\\ 9er\ s S˛pb&q9s 9a{Iږ6>}sQ͠?cj$\:_/M3JLX1mzr] ٗU~-aQDmrKҖ(K["LQJRy*J%XHݨݨHn$nTnn$n$nTnTn$r7Rp7r7Rp7⹛qQPԹ^ƹ3zzs%$\"~+f? "p5}=t^ʤ1!)#BC:w}_ GBz]H?w(-A4OHB)LN{ N I$8i|AZIT\TT7iGʫ.dxQ [goZcr 1m F{/@G1\+"R~&wr Z`-FNh?F{.n\m|==\#[.R/}VZXwfggfg]H@1LQmM՛%Ci}ܦ{X;NK5Nnkfkn#= b ET; KkӃ%%\7(RpZXgQ@ip0{j*B%9{ޤ)c~{EStGE{]dAh.E_жL۴!EvjKA~T]mxj_F{] ]Oh$@\M>aP?87P}+3L@1G8DDo9DO=;2YU{G!8]2?0Sjm+ z=!ZloBo\ڣX,NCqg"a@ŸTYE!5+F;aʌ~0&gj YrQ7!h"“u z1XU{OSoNJ\ONʱFrsjf6ҿ6T%Ofm"Fr۝Ė.+yRڻ`\bXgx`R# a;ɝ] ӖUGМh -h`icՍ6-&5mTYǶ4cG蘶ڔ[n隡R:$UZy` i ˰Ʈ+Aӱ(>۾_!6bF}n7"lNQ7T8翳] GCHi5õ )k(:%m#_=6,{Sj2F>Q$;YɽIM0>g%@˪ `nZ{#e2+qtd80\9ɩQPypM)m5ErbQ?$|&[=wYF[cEI[:foۻP膷hjwo$aRTDOlPu '+~!4ل~ 6vh#9`*v-0y}ơO'[M@ƥcuC  WDžrljb-7wqbL0 $ ӇCܗ~"bNJ{Du`_4kaQdhS!"R0Dj~y5,RKi=9|r7F2th0JfpY 馌mn$O!GG"L&YAHH.b&:<&Jҗ}L:c>4'}0!'oJ٭B?-uG3b}FXs± \FRQǎɴzZ4g &ጔVhh-.dӊaAQ0*@{ bf?yze_wk{eZYZ._,*j _r.ʻ(/Wν̯Ì+Yby]_+(/W߯{pa@b8~Y*<&j|]hNS6ն{f+ SU@l{6[$6-OU|p U5I ci{(9G|P$4&sgG%1w~]gsT !ǎ ~A~l?~?KQ@ xBq=MA%ΰ,ǃ_TW6vZCa琶RZ]N༨h>_M9a<+S3{hQN|J~ q%9Ğ\@چE/cco/27.J;c`/aB0,0G,q¡!<%mi+U`d M;*J=b7WFoM[ Q~/u} Gen3$Y{հU;gD  !\#(QqU>[Uk wWnuќ^)(DVθqqUX;f}fOJ/7kb1P[M^:;>r՞rFCv߻,,Hdo%dZl?)rF#Egw w/j Groff/troff grofftroff123456789 text.groffxYO8O(c1`Ђ{!*:MZ"Z/mbvmea@Jjɾ_s}ۄ.4ziP/WuMZ~n, pxŒ|n\q?"܄=~FI9{&pSroL 6X7c_cH0G%Mp}͇{c7GkS__ l˰v Q_;VQG-\2=DrmjYbeS﾿^iin 1?D8<s 1z=TJ]6Hufx =bsJW瑐2̆T(co f/gJ ڒOܹ-GʘLjֽP۠oӃr=!9ATTL㗣bs ~umSs"~"~4Ej _ %~vG`yMhK_t_?b]Gvoc0 E۔SJxEϳ=,j"/Iɜmlfx P.a-\cqK񙂬a zrp(Ԏ%!5hvc3a`ϳkU䝼^\nI{i(mہR's=W>yf1+讫]Ҏ3 W`W 0@;Љꚶ%CafYF+l"enniw6 |P2ơ)XE9&%tA 7AAsgIS32;o0,^3cS\!޶n\vj%:9gYcFZ*%e7muU8D]3Zē'Xcyzl:YM2 B,I梺!3,G+\c]ȲKS5EѼRg[lYN((K2iY[n$SWyxX ^Ҧs0DWRͧ|?Ff;}NXIEmQM.K꒢-Y)xJ! \NAX.t$ͭqt|yLIhe5h PV A}$ngO=4&N%ٱG:LKNt]jfSn;W b k1,YH&9%Se5ipQa6k yYfsEYMSle\Z2/f6򢪉$\5[Qz~|&29\S6ٚ^(ScN)XE˄:!U)-C<ؼ&kϢ_qaų` %yzaS71۽H5ƙR{;0Jr-YKsnՆr' )ŵq+!A!Mea?5hEeTv1(Dn& k=hhu^0X+i*YT:|~\+M2P⚗yiЕ ̞vKM]Y)B_SQY s0#R)R)iԧPruF=Uob$P{zxE JӉզ!M:wzIJ++ xZGo4r:BJhi&?jA67P/bO+qAщ=:prt""u U:傐Df@F` ?\\(\\,@py2Y pEUS%I"rOMQv2f-Rf#T/^aCBp˷(֋y$B.UlwWz??CSo-upP^*ף c@&E\ʞ h/Ҍ!~ӎbÉԏQ)$pM > -GeԞlڛ r]¡?*{6ޯd,ҵp{rYnR[ dz+ljeM#[Y>X6<< k&I:ɹZكW^-">"`{{x$L 벴+ LzlBn,%E7 y0 %6,lk"qeIL۸=͈ܿv"Sc!,| b]TIL7N9~O#޵?̍ɍ^hu鸍ۺ§cm8m0еwlчs1A~Jxu!6kc'$fMlzش6!5m%dl2}ɂt~4.;WSy":eaoۘ8v C됫b?$cd Bb~U9w89Oe 3fpD$nlbǦa 84bs0ڲ/\xm]ܘƉhlSyVu .CAK}wN =~XRT?)b,I'i( sےj) O@88" c* 1rAZg'ݝ2o m Bu7Ӄw!V/K3 PsW*pa\pU$ίyXקgW)+m$y% ZG"cdLYFlTc*-gBQ3)SqnqB,E$ @ng;6 +@'k-ر9ƪJq;{cۭgHGZ]cm][gUf 1{1apvj*_7>Mvoxv-4,cEAy=>1 ێ!UX+_8 dԁk;z^2s ݃Lr^*ȶnj :MCVY]vK(p! :$a=\Kgs!¨p(?9/1d_h!8m5ZK6noԪ0/z/DnO:&k~+;GF:Ys.tGM+ж{5[X,{CC%M*[V6Y g rDTr*ɮR+p=#C#" Dnw v)Qa3X(v%?6O XZ@3,m@x` g@^ȁu(v7'aGuAsp\uqZzL5㕂*W*w,l_xE^ߑ x3)n623{Ye^/X}z?&$كP|{M}pzdߜ~2G}sZ-+Oyw="WjZ~gLQ{)hostshosts source.hostsxڅQN0 m r@HHpCăhiRũѱ__&UVm/OIDž岤Bmhz<K"n 9O0j[K@'?D"dp@IGxF> 0'ao4`@^Y s0$EIt}¬d'IJ =Dw% O1 YE tM*"9_."@:AUp. [hj=*i낡ScMb(Z077a "psS\ދ"P-aʉz YoĚcfUxiQ| TѪFĶ@x]RɧEJsՖ|!5Eg( T )3f*ơY`;zOt5JϊhcDZŠӫY2;v4. 'qm8S`*6#OA,Ih˒͍)IaC,+ʊz;:}-CFKW= w؎HLX_1*~Ye~E\ğ *QqbP"iG1_dd k.oo 䋾⤽vKqV*$dx}V`PN9eG,^QU IJd'82CL֫^$J(TI,\ iiCƘn<'7sR`6WEq0Jr%ǽi+ n1YEmF|O%&'_Zؿ*ϊ$Xy#ZP(7 3wY̏+P cٔwI]/e Ğ&z" %iq*QK5]pS[N"U`ݠʻNW)UNI >"Ns+.HyA10іS?I]W}(1a,m2`$%t'*BUxT9i'\XQ6`ѥm/B%I[!#B6}e%5QuOQ EWS9 ք< i};\Q:9 ~e/5Z!\ɡJsw8ȸ d Gs;WyerfJyM62M,YJñF1@bqNE n|NK񉜜ׅCQe3i4_TUYg U{Lp掳DKy<X+ޢ`?] C;V1ę"-/4P`Hxӈ8p3t*ȣ<>}BEW_ڈűlGUz=OYR<73bP>z7K)E޾U,ܛg"fce"xђq`j2-L<(g#(5в!FMvܥI&:DlѬ6`x6̷p%[ /K aYGiձ\LeW9ķ |z)>;"畘's&}&j~\9+؎ya8CJ;9#>ѩt;ƣؽ%%Q9E{=i" ڌH#2Lٝh^(,4DN+TaV "\?4{c.p+ n,^Ha~nvʹ]WJI`=4-T]2ex]a؝=FEQn_-E|(R?VgfeSKyvvВ/Dp8I9Efs!]X]b$cs!嶢Pd SMEyGQn)?)ʟ+_(E(o+uEyWQ(W-}(P\,+8Vʋً$cb#2+'~lȑ4Y&4qD+˸MCɝIx*FK=˱-_J4a76 罔Yﲣ2FLrԟMjgy'mT&2 h!')0Ƌ ֖BSPebdZ$qT&ĉ/É7FT9gtB,{7mh' W/D+9ANqfd4MɀP2s=іxwT]\AUHQ\NK<5Mzz?!zzO{-n2OuW2fR zSsgVCSpefJyG꒛UϝiO5n!EOh1wwP!??*(d$_pr{u]8w;G"]rg~F2{GLYFba<=#l³6 ~C#]ewz?xWnR^BxnAق&׏87CrX;V>ձ&{; r)R9,CCeh{W' -rIL˳r3UR|{Ems |Bi7y%7%yT3=`v5 3Nz/*фe kW^ZBÞhqbs(7!u&U%68VCVsr/$bFq(:h6G9,=:ѹ ~_?/G~#q}",!:<<զR޹.,J8MWhϝVNf'e"}J+xq1S \1|Wz'iX&5 E_T{ܵ䝢,}KꄂAo޷a~Y3Qf{3S\+xL M ?ETCe? /֢4zbZv.ABq.7iU L+t@T艕e8Þ Ʀah2{--L9DKR⌁<?$Ѝ )_$ܷyڲdgdj.6Bts^ @>>pMcz=4QAfHx\D}I=h0l& N4p0#2B8>3`ŧͅ5]3ݗ#N&h!8䄝Ɯ"y:Z‹aK;7JcƑV[Ԕ 949"=$ 'mPi:NTq#mK5f^O"V1`E\TH'tfϨQ4T~tv*xD$d$S9lRDFဳm|]˓eY UsVB8s*E.hy4_IjLefx7oq,n젛`bWC"F/LEĆhA PĜ6<9Ϥ=U&vbyu1+I]xAbħBCJT8|`X I€dWOӖi4-.Z}ϷU).?ڤ3LdF 4]N PhCs/si)A]`ӹl\.3sjT=oj^+@u^`HĽ`[<H?[ۺĪHT3ps K.zenkm/*wtL6Z` 5x<.Ӿ~V}Le4}1tiJzA_ ޸{ޔ<Հ$9,7D(vM'7^ Cj:U}v1U@ <L9TXS+Е+>̹7oOݯe2^tr|=+;\Z+ +8U<'qܑHyӆ[dO@X% L ̿"隆{=c؎ӵ:];ə oحa7`Ƙĉpp8\tDb))=l9cLWkKt99SZ+])B1 ፊ@/-^^. O9oXA;ѭoZKK׵.xQX"0!t weAFA/ւ,9w͞.\.4Um24eq#)j/dv"8]FXm2$/6z_L_YT"[f5c՟ Ν qu,H" -륅_ʎ:~T*>Kn zXVvH7lQHw!2]1WW3҇إ[8&Us2 o-#êEݜIe1Mc!MvXKxف|ʛm&^CțbP>ĂC~LeSyY]a&,=K>ԟ2=-H_n(]zuU;hNJ7bQZX:Pj~QCŞMpGe^u?E[ɾr}iuWruN m9Z b ~_PΎYH$Ȏ_[P]QD4v0i8r~!5cS=/P,ci)RDB4t/ug[o_ͨ=Z~V:厝7_-mmyZfKC}G+mi> 7=a'm| LoN.%PsJZY91N8CPL,)StnN/7os\\y6գ14mXEb)xuL$K5Q6]t7zWoł+/cnO?D"7OTû`3Cfl,JU&R5h h7#51??]{i?Ody_cY5[ˇ=&9Gmg2ނd!>kXw|Nt3mڒc_THrt}- MǍOVt>r!o&]/#a%b9X wnlJpˆlM*iβ|#q|޲\؃3m .T Vi`@AP<:A(l sz=c}, Xq$WJ B'(n4\[V|*BcJɍaDd)3~E09ćVt%2旸 <ܲ{0\oX"HṸ}- |49_{ix\3rknQ;"q n08TD?l@fq&5jn`umx /lc7 44~P'FԈ{Ni"(UbQxЫp/Z^NT@uɧRh:JO$EnT v HTML (Jinja2)htm.j2html.j2xhtml.j2xml.j2text.html.jinja2^{% extends ["'][^"']+["'] %}qxڅ; DAz`aa-f%yN)nՠRN[3Io@n-R|K2č+?HABnV|'/6-^I/>QIG=YhJinja2j2jinja2jinja source.jinja2*x՘Ks63NGN&ʹuŒ҉v֊N9 DAb PAv*?^"n!% ~p6QH:!)u oۭzꄚJ.N%oX.RyX?[~F>O[O#Fi,EȽQ QtYF㣆5*z-$/=rɉtY pCDž8\T?2 r m sUڱT-`.X>y]E0}p^HCȈRBw>죁zʀ#r$XI yV UFQFTPɅEk wyZ_O?aR[Ppeo4\G$- enOۮmogoߞE]g;C_ٿSK3Y? ,dv:PC\kD)h}iO K+cL握W@!ķ& t[KOb #R=&ǘd7ujsonnetjsonnet libsonnet libjsonnetsource.jsonnetxWo6u[Y C7ly1%AӏؚvtPBRI/CeIT)<͆tǯx Cӎx^m'_09(Zqf^}ɍjQ~JI%֮/ ~Kb>Juliajl source.julia^#!.*\bjulia\s*$ base_funcsr\b(?:abs|abs2|abspath|accumulate|accumulate!|acos|acosd|acosh|acot|acotd|acoth|acsc|acscd|acsch|addenv|adjoint|all|all!|allequal|allunique|angle|any|any!|append!|argmax|argmin|ascii|asec|asecd|asech|asin|asind|asinh|asyncmap|asyncmap!|atan|atand|atanh|atexit|atreplinit|axes|backtrace|basename|big|bind|binomial|bitreverse|bitrotate|bitstring|broadcast|broadcast!|bswap|bytes2hex|bytesavailable|cat|catch_backtrace|cbrt|cd|ceil|cglobal|checkbounds|checkindex|chmod|chomp|chop|chopprefix|chopsuffix|chown|circcopy!|circshift|circshift!|cis|cispi|clamp|clamp!|cld|close|closewrite|cmp|coalesce|code_lowered|code_typed|codepoint|codeunit|codeunits|collect|complex|conj|conj!|contains|convert|copy|copy!|copysign|copyto!|cos|cosc|cosd|cosh|cospi|cot|cotd|coth|count|count!|count_ones|count_zeros|countlines|cp|csc|cscd|csch|ctime|cumprod|cumprod!|cumsum|cumsum!|current_exceptions|current_task|deepcopy|deg2rad|delete!|deleteat!|denominator|detach|devnull|diff|digits|digits!|dirname|disable_sigint|diskstat|display|displayable|displaysize|div|divrem|download|dropdims|dump|eachcol|eachindex|eachline|eachmatch|eachrow|eachslice|eachsplit|eltype|empty|empty!|endswith|enumerate|eof|eps|error|errormonitor|esc|escape_string|evalfile|evalpoly|exit|exp|exp10|exp2|expanduser|expm1|exponent|extrema|extrema!|factorial|falses|fd|fdio|fetch|fieldcount|fieldname|fieldnames|fieldoffset|fieldtypes|filemode|filesize|fill|fill!|filter|filter!|finalize|finalizer|findall|findfirst|findlast|findmax|findmax!|findmin|findmin!|findnext|findprev|first|firstindex|fld|fld1|fldmod|fldmod1|flipsign|float|floatmax|floatmin|floor|flush|fma|foldl|foldr|foreach|frexp|fullname|functionloc|gcd|gcdx|gensym|get|get!|get_zero_subnormals|gethostname|getindex|getkey|getpid|getproperty|gperm|hardlink|hasfield|hash|haskey|hasmethod|hasproperty|hcat|hex2bytes|hex2bytes!|homedir|htol|hton|hvcat|hvncat|hypot|identity|ifelse|ignorestatus|im|imag|in|include_dependency|include_string|indexin|insert!|insorted|instances|intersect|intersect!|inv|invmod|invperm|invpermute!|isabspath|isabstracttype|isapprox|isascii|isassigned|isbits|isbitstype|isblockdev|ischardev|iscntrl|isconcretetype|isconst|isdigit|isdir|isdirpath|isdisjoint|isdispatchtuple|isempty|isequal|iseven|isfifo|isfile|isfinite|isimmutable|isinf|isinteger|isinteractive|isless|isletter|islink|islocked|islowercase|ismarked|ismissing|ismount|ismutable|ismutabletype|isnan|isnothing|isnumeric|isodd|isone|isopen|ispath|isperm|ispow2|isprimitivetype|isprint|ispunct|isqrt|isreadable|isreadonly|isready|isreal|issetequal|issetgid|issetuid|issocket|issorted|isspace|issticky|isstructtype|issubnormal|issubset|istaskdone|istaskfailed|istaskstarted|istextmime|isunordered|isuppercase|isvalid|iswritable|isxdigit|iszero|iterate|join|joinpath|keepat!|keys|keytype|kill|kron|kron!|last|lastindex|lcm|ldexp|leading_ones|leading_zeros|length|lock|log|log10|log1p|log2|lowercase|lowercasefirst|lpad|lstat|lstrip|ltoh|macroexpand|map|map!|mapfoldl|mapfoldr|mapreduce|mapslices|mark|match|max|maximum|maximum!|maxintfloat|merge|merge!|mergewith|mergewith!|methods|min|minimum|minimum!|minmax|missing|mkdir|mkpath|mktemp|mktempdir|mod|mod1|mod2pi|modf|modifyproperty!|mtime|muladd|mv|nameof|names|nand|ncodeunits|ndigits|ndims|nextfloat|nextind|nextpow|nextprod|nonmissingtype|nor|normpath|notify|ntoh|ntuple|numerator|objectid|occursin|oftype|one|ones|oneunit|only|open|operm|pairs|parent|parentindices|parentmodule|parse|partialsort|partialsort!|partialsortperm|partialsortperm!|pathof|peek|permute!|permutedims|permutedims!|pi|pipeline|pkgdir|pointer|pointer_from_objref|pop!|popat!|popdisplay|popfirst!|position|powermod|precision|precompile|prepend!|prevfloat|prevind|prevpow|print|println|printstyled|process_exited|process_running|prod|prod!|promote|promote_rule|promote_shape|promote_type|propertynames|push!|pushdisplay|pushfirst!|put!|pwd|rad2deg|rand|randn|range|rationalize|read|read!|readavailable|readbytes!|readchomp|readdir|readeach|readline|readlines|readlink|readuntil|real|realpath|redirect_stderr|redirect_stdin|redirect_stdio|redirect_stdout|redisplay|reduce|reenable_sigint|reim|reinterpret|relpath|rem|rem2pi|repeat|replace|replace!|replaceproperty!|repr|reset|reshape|resize!|rethrow|retry|reverse|reverse!|reverseind|rm|rot180|rotl90|rotr90|round|rounding|rpad|rsplit|rstrip|run|schedule|searchsorted|searchsortedfirst|searchsortedlast|sec|secd|sech|seek|seekend|seekstart|selectdim|set_zero_subnormals|setcpuaffinity|setdiff|setdiff!|setenv|setindex!|setprecision|setproperty!|setrounding|show|showable|showerror|sign|signbit|signed|significand|similar|sin|sinc|sincos|sincosd|sincospi|sind|sinh|sinpi|size|sizehint!|sizeof|skip|skipchars|skipmissing|sleep|something|sort|sort!|sortperm|sortperm!|sortslices|splice!|split|splitdir|splitdrive|splitext|splitpath|sprint|sqrt|stacktrace|startswith|stat|stderr|stdin|stdout|step|stride|strides|string|strip|success|sum|sum!|summary|supertype|swapproperty!|symdiff|symdiff!|symlink|systemerror|take!|tan|tand|tanh|task_local_storage|tempdir|tempname|textwidth|thisind|time|time_ns|timedwait|titlecase|to_indices|touch|trailing_ones|trailing_zeros|transcode|transpose|trues|trunc|truncate|trylock|tryparse|typeintersect|typejoin|typemax|typemin|unescape_string|union|union!|unique|unique!|unlock|unmark|unsafe_copyto!|unsafe_load|unsafe_pointer_to_objref|unsafe_read|unsafe_store!|unsafe_string|unsafe_trunc|unsafe_wrap|unsafe_write|unsigned|uperm|uppercase|uppercasefirst|valtype|values|vcat|vec|view|wait|walkdir|which|widemul|widen|withenv|write|xor|yield|yieldto|zero|zeros|zip|applicable|eval|fieldtype|getfield|invoke|isa|isdefined|modifyfield!|nfields|nothing|replacefield!|setfield!|swapfield!|throw|tuple|typeassert|typeof|undef|include)(?!{{symb_id}}) base_macros\b(?:NamedTuple|__DIR__|__FILE__|__LINE__|__MODULE__|__dot__|allocated|assert|async|atomic|atomicreplace|atomicswap|boundscheck|ccall|cfunction|cmd|coalesce|debug|deprecate|doc|elapsed|enum|error|eval|evalpoly|fastmath|generated|gensym|goto|inbounds|info|inline|isdefined|label|lock|macroexpand|macroexpand1|noinline|nospecialize|polly|show|showtime|simd|something|specialize|static|sync|task|threadcall|time|timed|timev|view|views|warn)base_module_funcs\b(?:Base\.(?:abs|abs2|abspath|accumulate|accumulate!|acos|acosd|acosh|acot|acotd|acoth|acsc|acscd|acsch|addenv|adjoint|all|all!|allequal|allunique|angle|any|any!|append!|argmax|argmin|ascii|asec|asecd|asech|asin|asind|asinh|asyncmap|asyncmap!|atan|atand|atanh|atexit|atreplinit|axes|backtrace|basename|big|bind|binomial|bitreverse|bitrotate|bitstring|broadcast|broadcast!|bswap|bytes2hex|bytesavailable|cat|catch_backtrace|cbrt|cd|ceil|cglobal|checkbounds|checkindex|chmod|chomp|chop|chopprefix|chopsuffix|chown|circcopy!|circshift|circshift!|cis|cispi|clamp|clamp!|cld|close|closewrite|cmp|coalesce|code_lowered|code_typed|codepoint|codeunit|codeunits|collect|complex|conj|conj!|contains|convert|copy|copy!|copysign|copyto!|cos|cosc|cosd|cosh|cospi|cot|cotd|coth|count|count!|count_ones|count_zeros|countlines|cp|csc|cscd|csch|ctime|cumprod|cumprod!|cumsum|cumsum!|current_exceptions|current_task|deepcopy|deg2rad|delete!|deleteat!|denominator|detach|devnull|diff|digits|digits!|dirname|disable_sigint|diskstat|display|displayable|displaysize|div|divrem|download|dropdims|dump|eachcol|eachindex|eachline|eachmatch|eachrow|eachslice|eachsplit|eltype|empty|empty!|endswith|enumerate|eof|eps|error|errormonitor|esc|escape_string|evalfile|evalpoly|exit|exp|exp10|exp2|expanduser|expm1|exponent|extrema|extrema!|factorial|falses|fd|fdio|fetch|fieldcount|fieldname|fieldnames|fieldoffset|fieldtypes|filemode|filesize|fill|fill!|filter|filter!|finalize|finalizer|findall|findfirst|findlast|findmax|findmax!|findmin|findmin!|findnext|findprev|first|firstindex|fld|fld1|fldmod|fldmod1|flipsign|float|floatmax|floatmin|floor|flush|fma|foldl|foldr|foreach|frexp|fullname|functionloc|gcd|gcdx|gensym|get|get!|get_zero_subnormals|gethostname|getindex|getkey|getpid|getproperty|gperm|hardlink|hasfield|hash|haskey|hasmethod|hasproperty|hcat|hex2bytes|hex2bytes!|homedir|htol|hton|hvcat|hvncat|hypot|identity|ifelse|ignorestatus|im|imag|in|include_dependency|include_string|indexin|insert!|insorted|instances|intersect|intersect!|inv|invmod|invperm|invpermute!|isabspath|isabstracttype|isapprox|isascii|isassigned|isbits|isbitstype|isblockdev|ischardev|iscntrl|isconcretetype|isconst|isdigit|isdir|isdirpath|isdisjoint|isdispatchtuple|isempty|isequal|iseven|isfifo|isfile|isfinite|isimmutable|isinf|isinteger|isinteractive|isless|isletter|islink|islocked|islowercase|ismarked|ismissing|ismount|ismutable|ismutabletype|isnan|isnothing|isnumeric|isodd|isone|isopen|ispath|isperm|ispow2|isprimitivetype|isprint|ispunct|isqrt|isreadable|isreadonly|isready|isreal|issetequal|issetgid|issetuid|issocket|issorted|isspace|issticky|isstructtype|issubnormal|issubset|istaskdone|istaskfailed|istaskstarted|istextmime|isunordered|isuppercase|isvalid|iswritable|isxdigit|iszero|iterate|join|joinpath|keepat!|keys|keytype|kill|kron|kron!|last|lastindex|lcm|ldexp|leading_ones|leading_zeros|length|lock|log|log10|log1p|log2|lowercase|lowercasefirst|lpad|lstat|lstrip|ltoh|macroexpand|map|map!|mapfoldl|mapfoldr|mapreduce|mapslices|mark|match|max|maximum|maximum!|maxintfloat|merge|merge!|mergewith|mergewith!|methods|min|minimum|minimum!|minmax|missing|mkdir|mkpath|mktemp|mktempdir|mod|mod1|mod2pi|modf|modifyproperty!|mtime|muladd|mv|nameof|names|nand|ncodeunits|ndigits|ndims|nextfloat|nextind|nextpow|nextprod|nonmissingtype|nor|normpath|notify|ntoh|ntuple|numerator|objectid|occursin|oftype|one|ones|oneunit|only|open|operm|pairs|parent|parentindices|parentmodule|parse|partialsort|partialsort!|partialsortperm|partialsortperm!|pathof|peek|permute!|permutedims|permutedims!|pi|pipeline|pkgdir|pointer|pointer_from_objref|pop!|popat!|popdisplay|popfirst!|position|powermod|precision|precompile|prepend!|prevfloat|prevind|prevpow|print|println|printstyled|process_exited|process_running|prod|prod!|promote|promote_rule|promote_shape|promote_type|propertynames|push!|pushdisplay|pushfirst!|put!|pwd|rad2deg|rand|randn|range|rationalize|read|read!|readavailable|readbytes!|readchomp|readdir|readeach|readline|readlines|readlink|readuntil|real|realpath|redirect_stderr|redirect_stdin|redirect_stdio|redirect_stdout|redisplay|reduce|reenable_sigint|reim|reinterpret|relpath|rem|rem2pi|repeat|replace|replace!|replaceproperty!|repr|reset|reshape|resize!|rethrow|retry|reverse|reverse!|reverseind|rm|rot180|rotl90|rotr90|round|rounding|rpad|rsplit|rstrip|run|schedule|searchsorted|searchsortedfirst|searchsortedlast|sec|secd|sech|seek|seekend|seekstart|selectdim|set_zero_subnormals|setcpuaffinity|setdiff|setdiff!|setenv|setindex!|setprecision|setproperty!|setrounding|show|showable|showerror|sign|signbit|signed|significand|similar|sin|sinc|sincos|sincosd|sincospi|sind|sinh|sinpi|size|sizehint!|sizeof|skip|skipchars|skipmissing|sleep|something|sort|sort!|sortperm|sortperm!|sortslices|splice!|split|splitdir|splitdrive|splitext|splitpath|sprint|sqrt|stacktrace|startswith|stat|stderr|stdin|stdout|step|stride|strides|string|strip|success|sum|sum!|summary|supertype|swapproperty!|symdiff|symdiff!|symlink|systemerror|take!|tan|tand|tanh|task_local_storage|tempdir|tempname|textwidth|thisind|time|time_ns|timedwait|titlecase|to_indices|touch|trailing_ones|trailing_zeros|transcode|transpose|trues|trunc|truncate|trylock|tryparse|typeintersect|typejoin|typemax|typemin|unescape_string|union|union!|unique|unique!|unlock|unmark|unsafe_copyto!|unsafe_load|unsafe_pointer_to_objref|unsafe_read|unsafe_store!|unsafe_string|unsafe_trunc|unsafe_wrap|unsafe_write|unsigned|uperm|uppercase|uppercasefirst|valtype|values|vcat|vec|view|wait|walkdir|which|widemul|widen|withenv|write|xor|yield|yieldto|zero|zeros|zip)|Broadcast\.(?:broadcast|broadcast!|broadcast_axes|broadcastable|dotview)|Docs\.(?:doc)|GC\.(?:)|Iterators\.(?:countfrom|cycle|drop|dropwhile|enumerate|flatten|partition|product|repeated|rest|take|takewhile|zip)|Libc\.(?:calloc|errno|flush_cstdio|free|gethostname|getpid|malloc|realloc|strerror|strftime|strptime|systemsleep|time|transcode)|MathConstants\.(?:catalan|e|eulergamma|golden|pi)|Meta\.(?:isbinaryoperator|isexpr|isidentifier|isoperator|ispostfixoperator|isunaryoperator|quot|replace_sourceloc!|show_sexpr)|StackTraces\.(?:stacktrace)|Sys\.(?:cpu_info|cpu_summary|free_memory|isapple|isbsd|isdragonfly|isexecutable|isfreebsd|isjsvm|islinux|isnetbsd|isopenbsd|isunix|iswindows|loadavg|total_memory|uptime|which)|Threads\.(?:atomic_add!|atomic_and!|atomic_cas!|atomic_fence|atomic_max!|atomic_min!|atomic_nand!|atomic_or!|atomic_sub!|atomic_xchg!|atomic_xor!|nthreads|threadid)|Core\.(?:applicable|eval|fieldtype|getfield|invoke|isa|isdefined|modifyfield!|nfields|nothing|replacefield!|setfield!|swapfield!|throw|tuple|typeassert|typeof|undef))(?!{{symb_id}}) base_modulesb\b(?:Base|Broadcast|Docs|GC|Iterators|Libc|MathConstants|Meta|StackTraces|Sys|Threads|Core|Main)\b base_types\b(?:AbstractArray|AbstractChannel|AbstractChar|AbstractDict|AbstractDisplay|AbstractFloat|AbstractIrrational|AbstractMatch|AbstractMatrix|AbstractPattern|AbstractRange|AbstractSet|AbstractString|AbstractUnitRange|AbstractVecOrMat|AbstractVector|Any|ArgumentError|Array|AssertionError|BigFloat|BigInt|BitArray|BitMatrix|BitSet|BitVector|Bool|BoundsError|CanonicalIndexError|CapturedException|CartesianIndex|CartesianIndices|Cchar|Cdouble|Cfloat|Channel|Char|Cint|Cintmax_t|Clong|Clonglong|Cmd|Colon|Complex|ComplexF16|ComplexF32|ComplexF64|ComposedFunction|CompositeException|ConcurrencyViolationError|Condition|Cptrdiff_t|Cshort|Csize_t|Cssize_t|Cstring|Cuchar|Cuint|Cuintmax_t|Culong|Culonglong|Cushort|Cvoid|Cwchar_t|Cwstring|DataType|DenseArray|DenseMatrix|DenseVecOrMat|DenseVector|Dict|DimensionMismatch|Dims|DivideError|DomainError|EOFError|Enum|ErrorException|Exception|ExponentialBackOff|Expr|Float16|Float32|Float64|Function|GlobalRef|HTML|IO|IOBuffer|IOContext|IOStream|IdDict|IndexCartesian|IndexLinear|IndexStyle|InexactError|InitError|Int|Int128|Int16|Int32|Int64|Int8|Integer|InterruptException|InvalidStateException|Irrational|KeyError|LazyString|LinRange|LineNumberNode|LinearIndices|LoadError|MIME|Matrix|Method|MethodError|Missing|MissingException|Module|NTuple|NamedTuple|Nothing|Number|OrdinalRange|OutOfMemoryError|OverflowError|Pair|PartialQuickSort|PermutedDimsArray|Pipe|ProcessFailedException|Ptr|QuoteNode|Rational|RawFD|ReadOnlyMemoryError|Real|ReentrantLock|Ref|Regex|RegexMatch|Returns|RoundingMode|SegmentationFault|Set|Signed|Some|StackOverflowError|StepRange|StepRangeLen|StridedArray|StridedMatrix|StridedVecOrMat|StridedVector|String|StringIndexError|SubArray|SubString|SubstitutionString|Symbol|SystemError|Task|TaskFailedException|Text|TextDisplay|Timer|Tuple|Type|TypeError|TypeVar|UInt|UInt128|UInt16|UInt32|UInt64|UInt8|UndefInitializer|UndefKeywordError|UndefRefError|UndefVarError|Union|UnionAll|UnitRange|Unsigned|Val|Vararg|VecElement|VecOrMat|Vector|VersionNumber|WeakKeyDict|WeakRef)\blong_opw(?:\+=|-=|\*=|/=|//=|\\\\=|^=|÷=|%=|<<=|>>=|>>>=|\|=|&=|:=|=>|$=|\|\||&&|<:|>:|\|>|<\||//|\+\+|<=|>=|->|===|==|!==|!=)symb_idH(?:[^\s{{symb_lang}}{{symb_op}}0-9](?:[^\s{{symb_lang}}{{symb_op}}]|!)*) symb_lang(?:[(){}\[\],.;:'"`@#])symb_op)(?:{{symb_op_ascii}}|{{symb_op_unicode}}) symb_op_ascii[-+*/\\=^:<>~?&$%|!]symb_op_unicodea[≤≥¬←→↔↚↛↠↣↦↮⇎⇏⇒⇔⇴⇶⇷⇸⇹⇺⇻⇼⇽⇾⇿⟵⟶⟷⟷⟹⟺⟻⟼⟽⟾⟿⤀⤁⤂⤃⤄⤅⤆⤇⤌⤍⤎⤏⤐⤑⤔⤕⤖⤗⤘⤝⤞⤟⤠⥄⥅⥆⥇⥈⥊⥋⥎⥐⥒⥓⥖⥗⥚⥛⥞⥟⥢⥤⥦⥧⥨⥩⥪⥫⥬⥭⥰⧴⬱⬰⬲⬳⬴⬵⬶⬷⬸⬹⬺⬻⬼⬽⬾⬿⭀⭁⭂⭃⭄⭇⭈⭉⭊⭋⭌←→≡≠≢∈∉∋∌⊆⊈⊂⊄⊊∝∊∍∥∦∷∺∻∽∾≁≃≄≅≆≇≈≉≊≋≌≍≎≐≑≒≓≔≕≖≗≘≙≚≛≜≝≞≟≣≦≧≨≩≪≫≬≭≮≯≰≱≲≳≴≵≶≷≸≹≺≻≼≽≾≿⊀⊁⊃⊅⊇⊉⊋⊏⊐⊑⊒⊜⊩⊬⊮⊰⊱⊲⊳⊴⊵⊶⊷⋍⋐⋑⋕⋖⋗⋘⋙⋚⋛⋜⋝⋞⋟⋠⋡⋢⋣⋤⋥⋦⋧⋨⋩⋪⋫⋬⋭⋲⋳⋴⋵⋶⋷⋸⋹⋺⋻⋼⋽⋾⋿⟈⟉⟒⦷⧀⧁⧡⧣⧤⧥⩦⩧⩪⩫⩬⩭⩮⩯⩰⩱⩲⩳⩴⩵⩶⩷⩸⩹⩺⩻⩼⩽⩾⩿⪀⪁⪂⪃⪄⪅⪆⪇⪈⪉⪊⪋⪌⪍⪎⪏⪐⪑⪒⪓⪔⪕⪖⪗⪘⪙⪚⪛⪜⪝⪞⪟⪠⪡⪢⪣⪤⪥⪦⪧⪨⪩⪪⪫⪬⪭⪮⪯⪰⪱⪲⪳⪴⪵⪶⪷⪸⪹⪺⪻⪼⪽⪾⪿⫀⫁⫂⫃⫄⫅⫆⫇⫈⫉⫊⫋⫌⫍⫎⫏⫐⫑⫒⫓⫔⫕⫖⫗⫘⫙⫷⫸⫹⫺⊢⊣⊕⊖⊞⊟∪∨⊔±∓∔∸≂≏⊎⊻⊽⋎⋓⧺⧻⨈⨢⨣⨤⨥⨦⨧⨨⨩⨪⨫⨬⨭⨮⨹⨺⩁⩂⩅⩊⩌⩏⩐⩒⩔⩖⩗⩛⩝⩡⩢⩣÷⋅∘×∩∧⊗⊘⊙⊚⊛⊠⊡⊓∗∙∤⅋≀⊼⋄⋆⋇⋉⋊⋋⋌⋏⋒⟑⦸⦼⦾⦿⧶⧷⨇⨰⨱⨲⨳⨴⨵⨶⨷⨸⨻⨼⨽⩀⩃⩄⩋⩍⩎⩑⩓⩕⩘⩚⩜⩞⩟⩠⫛⊍▷⨝⟕⟖⟗↑↓⇵⟰⟱⤈⤉⤊⤋⤒⤓⥉⥌⥍⥏⥑⥔⥕⥘⥙⥜⥝⥠⥡⥣⥥⥮⥯↑↓]y<xi5pf3+v 6$pO&IR WWujfߗN}3~ ̫⾻TKWۉ qҒJRI*ITJwa`_9\fG?yuŷ?*!OTǵuN>8W5,xb a *vѓI,ږWnM 63eZ#FLuK4 ]u> ^uif2\p1Di{uc(+6 ALuqV2gXrJw5VP<]KN+(59hziEUKQvJ*[L ַ€Qw;;ݝiq FwCekL+۷FJꪖn߲e(. ]iQiY2Rk`hd@ymeM(s $OtB~VXڐH=(u[W;5 1C0ԉ;ԽTKc ND&f5aO" 7q{`*sTNWϟ&㙺Ht߽*x~/͓oBOVzx@`_226 vG`e0V>--}ZvdVO|F)DG}Ii9t/ K0<ؿJlK;t$ kڄh 싖Oas_Sj[4T0(KF&R_cϤh$g1o.i+D3-w0ЫUfZQI ù &E{5EDlS EqM'e'LVM U㑞ܛx )/ћaj_C?jDg=@^Z;UurٲEJb|(YǦp2r^NZEڑ?sZ ơ[kɡ;ғ=|_oצD7r~W$g+C߾U"s5 E4AԂXnOΞ`^n|(6I"]Tca0$!+兲sS lEsy>VlM-2"n/NMUD","uag;0͙ΕBnF75N*`:$)Ng(dgƵQ!'le*3:n:.Jܚs 틢~Ҳ"=ڐwH\sȳ1N'u˺$2%F5M..(R_GZb8ɂbm .ύfT.7&Q>o]H0mX۾Mov BԊBa/;00;r(>JC#,qOD4U_DMyx8Qg2&\ղ)zOgGӗQYӿ݈ዣ9GX{v׬+e woѫ29G#OÑgp8:y Gށ'pY8!~G?pK8~>cñOp 8% } Ǿaa?GaQ '`I 柅`y_Wa5?ð,0 sPaIXxaXx^`, 6,EXB`,wp=8.xN|'#81N| '>É/ėp+85N'ɇp(89'pѿ}S7KPy*CM VaQ;s0 = ss0*̢.0 ~_W05TCPy*@TBe*A GTʓPy *OC< GP*@S|ϡTWPD~s0=sGasU{构`i3`̽s{0>}sG01}Ǡ$TsP}/@E՗ T WTAu7T硺շzPA%I!T?PŻ| Ϡ9TP _C+,Op8  PCm j'vj@/P{jA}}>'PjAs}/Ծ7P@A?ԏ@(g^ԫPAq?'ԟ3PAy_+P3_kP?ס߄[P߆q/B:ԗ~'w߇PAcO?P_AkK ,=K#t,,U`i(,=K$,=KO3,,=K ",Kҧ,}so[0=s1"=?30,b{*X/`P}ǿYX|߂yX\ŷa8,.b 밸'`$,"> P{jOCY= ^P{jo#0__`E{ ^W`u{0%Gb:0T@(T+PjB > Ǟ?O`(, ,~ǰ>Eꗰ5~=VcP{jOA=2 נv j1xcx=Ǟc/3pw{0? *? ؝V`QXx `YXx^`ϰ*,`, ,o͉CDl"6MĦ@nE"@P(RM&bDl7&I$tH!I$‰M&bDljM Qg7;~tc[m4_Z/=lw[ s֋u$@@ $@@*Dl"6MU><ﶦmk|s=G:P_۶P*\B.!K%:Hl"6M&bShhIjl =1TlZV coնZ.E^K{i.I$QDAIM&bt+uOYm[繢-Lw8~:HXYwK]Ӎf,hz3[xů ODzrvcʺMrw"]TwESiE?ꋂ ByuXvmFclwH $^LП|ƒvmvسD4xްԉmœ[TsCJ錔{i\~۹N qsȿp}{c2JRrF|yHGIA*J(.nW0A?^RX.Um﷛UEDC9>[qᠻcqՄwӵB~M7'CךrPyBbe9Nˏkti,(4ݗy=x=FiFiFiffM&bDl9u#F|_,<G/0=m:~:m_wXrn#` q8ALbDl"6VCr̭L8A N'Il"6M&bәT0}ӈoƙUC񜴗JugڬmMJC*/$VXAbM&bش*c}{P`D5) ~O۱<;ΚNT[yIEٛGI 郤>H 4cbDl"6e-TKEUfY 7[G w Y t:߬R>[wnh塪)K,wKC޶MUӔk=vε)[%?$u,`l(=ʢA0;ܯ+ tGlF<7YqH"+H'6M&bi%jm{lV({;/w|4Y/gwHO4]weryi8\6Jx8޻҉%R+ xs7𜧺l%'"eMiH\s\~R,3s?Ii,^lkq݈gĭTtmXՒhb2+[efGChm6 wᕁFLFm7^{J afCx4|e&g6ڔ6VI|elM6dz<hE#k$zݙ!S)4YE6RbԚߏwéF]fքt~_9Ap}g9}yN1l]sfJyp6nmۘSl֧5Ŵ>67 ilT2y_n*kF~¯!Ni`a-tE8Fx"(ljcNK~hki@GnHS̨q 00`ƿ+C-hSbY>F~ J1?6ltCe9CūxꢶAWgl>-<}XdF0^P q۸nyIwG%8<^%XImm&aްI7wóhY4얽n;kq[nۺg7:l>vo߻ֽ[vݲso{ kms`?c};0)E"hןn8T}f9gL[9̨tÇ?~߅fxGrӹ{7ݗ<˭FL۝hXhXhX\M&bDl42 džP(E"Dl"6MĦ2X?o`cG#pD8ҾM&bDl+E"@Dl"6M&bL]u7q&ΥpD8"GDl"6M+о262ݨ`lqnOCsgޙ9:g|svW'6urZsiggljq8q8N:&Dl"6MgP0'[{w/FwM ` hA a0H$Dl"6MĦ* /sl,ϭ?rwf_?|,?4r<˯,ώp d曇0Gfaio`XiiN:Z-`H1Úb6w8˲ ' ;t, 8S*"'y4*)XOy)Gыt8!a UTP4D&,\qΓ@߷,9f[+8qJ-4B,ʶ(p+8miEuSLp̌ƊlEC` mIG56iz؈4P@(Ϸлnڪ鎨PcX6pϥl(3-+]v{{RlV&M0 W:$%Q{˶_3goslΔυ^IVÙmc֥Y|Sʘ?I(([F,5.녹I=Y(L|Mv?/(*Ib8X -^`"_Syr|U(8, ,N (Y0/LS[T ߋLMMێ+]8D;O?.aQ?6mmG|Ke X\ߔ7]R0b;@)25:\Wxı$D#RXv7b+`: *̢5KmvCm\p%MxhrlO1Q/]6lx̰CV[:p3N (he!lTmrڲAB/,xwhKIe`IKz* ^\ fA.+2;p*DGZ+zfJfX -?YұDijqJ@;] rl*"&B/;8]EfYf&(Y9ekjl1ⱼ >l [$@8eeQX|,|ȱ+~r0=;ubF:l/%Qݎ +yJ62*qF1J]q/Фn¢Ǝ\JAZ#>(D}, D&kG2 .S~a%nh{21@xK3h"̲07F=\~kį!4wq^RTą*T43ps@dE5>iE??bG`K5ֶ"~ߒ\0V,GQvp[4!ZliBuiFX`_Fd,ы8pQ4C _(K3'Ìz˜8 P0GԤa -uq_:4PQ / x–RpG:5!d- Ym!|`]E;Nmӡthy<t+6C7+ 4DG&NKa+BH#-%_qɕ:ڶHIvjwK˖' ST[d~G5Pdsb-~w3bA/G4;a`햶ڊM]˅U<= Œq(14r%ISa肰)KUBqGaMэHʉ|1MGw@ }#ʛ).lRB("d&B6{fHL0oCy ϔg:J#=!0ւ ܢlY(H<L(EnVߨS>S>=&bDl"6uWqhE QGZ$/\-8쒸ڳ6Z-{o5^ޒo_[5^oA5$2# ߗ򫔳_'r/b}>AO'BJl"6M&bS{t ",2Pe_y%6Fb"6bDl"6:M$F{&6 Rg~>ڲ4}};zڞkbpo:''Tjb5XM&=Dl"6MĦNz$G&N"DQ(BO&bDl"6[Fs>.VD+ъhE"MDl"6M&"DQ(BO&bDl"6DTlވ"ZVD+iR&bDl"6u֤262NE{Nt3Ea@E&l|I{LY61P&q ZS{C)>UڈeGӅCQO'I-%6M&bGQ(B!'bDl"6FKPE"TH"6M&bfQ(B!EDl"6M= E[tU*B(bDl"6Nw19grfWvY;x8hh[^y(ϊzl3hx>'HܖsÍAh-&I$iAIM&bQ ÙPC0'[W*+Rb.unmp}0J;H#>h@Dl"6MĦN|x2 YO5ī󁏁kQ8 G:&mCmky#-Qk'ɹh$C)5% SuNj;^Tw8$vi$C" 0$C5Dl"6M]jşU69E\Qkq$z=]4L_&lHU{;dD25$rJ .8gILB $DAB)&bDl"6 ;Է?z{@FL?GDl"6MdדY vE x ==/+:&g:]$yM4mR>oh74M|N߆w{e;}];c|=4E[1;.Ӹ8vњ1UVI- Ee3yLQǙ:U#jZҸj)s0^ͲmfJ;2+qfۖ٤bHlW-)8P]L-qd&㺣:4n(yfW'7tY1{#7 i9eꊡ8[Sp㎎q,|Y1ߎZ岼\řM * M|RgSpb\熢.k|zPݩ/үkQeid{u`O*._ ;^YPXh[mHWI^%yUWI^bDl"6::a,M) q;gGq^n*0秽.-c4MwDJU# ֶZ;Rߞr{R?rrI9oy30SΏSj3uvJO9PS{) O G7T2,Yܞu>02]b]/0>BWlfv$⒴(ʊ|WA FHPnCfyn}_ÿ,3I_7gȈY~ugyv̈9ίo#u܆.=Fx6_(ѾmK 9)ھgj9Me=s O z,py2m#R{H!ԞoO!C~ 'D`"0"pھݒ41lc3-([iJʹXL,&i Dl"6M].P/ܛ ua/Zn.'4|vY-Ur`)D A O'I%6M&bioPW_Q=<]fCFmJ|;ՔM$_v}oy?IiЬS|ǛPշv) iSߟrze:|ڻ'^ƐK/I$K1&bDl"6h4&mv9SC=-gvI{TYW7?i Taiw wW@pF7];6Y.L/1eʔ|,ڐfybcۦD^ 3:xQPV7,ZM9'Mr[$N;$NJ&bDl"6u0A0ukjnljJ9u;YoZƦJ$P@I% $P`Dl"6M++7X"kڨ-'owy mEOO&?@$M&bI$6M&b锧'Y2=*Nd7ӓuQ{5|ު8o-ES-n;]il*߭,qun,EUԉ2qq)÷Y6Cۆ>Ρ˖}ކ.CM/2%пض2ãmi2#~lGuՍ8e#RWw#UQa٨#[oW\,I3:ޫEc|Sػcik֜-Ywf'E?+i +("tFHZA[-@35ǏdbZ*NScṲL1F6v+tMo+dOH>m:P%ez ]E#MUҰVVl{-o7l7(ô[I'tmgTg-Ci^tOٵ5PrƱ2a6ϧ'35r&-s6% SALW/tN'(v놃߭;%Y[Ƥ1?ۭ6Y߽c-J\:ew-SV=8esY9H D%ac/+ٿ{߹o fKe.A0wj2*E5?ܥ X3Xvl[؄BݸwҺI241]hӬȤOf^ms9]:?6{ffI[Hm^),?T!n{|Z5gs~yD]jL&8Zؕ'[q"pDzS3|l}ƏR;;G"bAq>gE`L¼٢ j1e ;ƛg쳹{5_xt5˄Ҹ*MH 9 3H 9_PٲANbڮ/v#8aҏMФ MФ MФ MM&biŸFA Y>Y~_,'Y~Uoޜ##FgY>>0Z2#N8jy {o]sr,~ O $@b@O$/+T1\^Q%_;n| k})@xV'͗Né"7Co[&'aDj? 90 #kR 5c%ÒOK򎞶]f$@hH0"#H0kbDl"6:LJj#uԥFMѐhH4$ M&bQSv3|`dXGڄ]L*h9)yi|6O&wl]Q4r{渙E1`L0&Hl"6M&bӊ7^f[I,Kotlinktkts source.Kotling xZ{s۸y\×_VD;ϻؖ54mtک8٬iRGRv4rl]oJ,RbXv'ζi#꿕GLM=dd/(T)KJ6]J+%{M'od>vE.$[9:a߷\'Cz1ȑ dvTA,}J! Bfkׅ,3c| Mt~|vQ 3]o}vf;)[ {.~YH/#h𠊒pg9;3dwdl)^q9n[P)]\ 4_BĴ\(!b:. ,ԱrPAd[ d= d2lhl,sп,>mp >~=N }b $A  CIV9 c)3NsAI_pZ}K}ɍe_q1;k\pKARQB)~( @5Nnz.=&%h%ME[j5bk$<p _j 0bL&dF]gaOsr&G#ϒf՘(&k|kd2ʎS9 K]c枘d!m.+-BiGRWJ\)ԕ6qD ةF(8lPyhefC=\z.ǨS2)'E:nDI)+tf<Of`nSbа`!> 2d#SsPtI9!^5rB&U-LjX֎+וs>uCFy9GAwꖡoMc!?*3yHf]1NiՋugg*{S%UN /ͯc%.W͓-YoENi2eSS1e#Ē;9ݓs}pGHbqMR%͸Π2p!׃] |N~~~ 7 ]R<7;B"|G[cH5~Qт*/:W6Y;:3\iK7wy.|7aTؐL-ýЂZRtB3Tc6-0y]0,&^oCFatd?~6$rlc`ݼ,HR\ =vfy"(9@Y*rOb/JG:}5cɊ$>S(N|"E]8ri0E I,D3?"l(5d)7ִh۬iR<)BcFF*D˝i2胃HU]Y1 Ylrè8 4}0ޮr$=jy6%ż&=d8&?dhIJk#ggU |wIwu&l2MT?h M45uQk%rEmmcsNrFP$H $+v]rϔe٣To;[U,~ :"N^EVPssUïj 8i~C-S!3g!mSO\k:7>.n Znh'iu }@v$}: \aGU.AgCA W; r7\.C" !.  e{$tDF$tad[ȏpnKZjeL&. +Cb܍X<0v9HxGYJGYOGzpײ!]4 m80x3VJd(9!!2W7$|u.tӂ xUv1ϵH+Fl7SUn$̳|2{lR> ״u I=eVD+3,r`GF4>a$eF+0T,d,}` ޲̘4&1FjXYE6] Ƹ"Fo%u#MFBcQeg'݃tSk^OuA@PS#KKĀ>b;_/%+Ge-dj{Z؅ \Ô8|TcCAʐ /%:ծ(^O;zyCJhzpXH&*.9w3NivA2uI{SLޒ~qaez:xǪ!qJ1JH%^) św@zl\%s [gHZJwp26FNwQ񋼳]vaG~¶;Ķ Z wew- lo= P.##He+ E6`g'0\A|q[|>JnC j`mR& r1w |bк X]h[ 67rRV_aՋ{5!Lesslesscss.less source.lessabsolute_lengths(?i:cm|mm|q|in|pt|pc|px|fr) angle_units(?i:deg|grad|rad|turn) combinators(?:>{1,3}|[~+])counter_styles#(?xi: arabic-indic | armenian | bengali | cambodian | circle | cjk-decimal | cjk-earthly-branch | cjk-heavenly-stem | decimal-leading-zero | decimal | devanagari | disclosure-closed | disclosure-open | disc | ethiopic-numeric | georgian | gujarati | gurmukhi | hebrew | hiragana-iroha | hiragana | japanese-formal | japanese-informal | kannada | katakana-iroha | katakana | khmer | korean-hangul-formal | korean-hanja-formal | korean-hanja-informal | lao | lower-alpha | lower-armenian | lower-greek | lower-latin | lower-roman | malayalam | mongolian | myanmar | oriya | persian | simp-chinese-formal | simp-chinese-informal | square | tamil | telugu | thai | tibetan | trad-chinese-formal | trad-chinese-informal | upper-alpha | upper-armenian | upper-latin | upper-roman )custom_element_chars`(?x: [-_a-z0-9\x{00B7}] | \\\. | [\x{00C0}-\x{00D6}] | [\x{00D8}-\x{00F6}] | [\x{00F8}-\x{02FF}] | [\x{0300}-\x{037D}] | [\x{037F}-\x{1FFF}] | [\x{200C}-\x{200D}] | [\x{203F}-\x{2040}] | [\x{2070}-\x{218F}] | [\x{2C00}-\x{2FEF}] | [\x{3001}-\x{D7FF}] | [\x{F900}-\x{FDCF}] | [\x{FDF0}-\x{FFFD}] | [\x{10000}-\x{EFFFF}] )custom_elementsS\b([a-z](?:{{custom_element_chars}})*-(?:{{custom_element_chars}})*)\b(?!{{ident}})duration_units (?i:s|ms) element_names\b(a|abbr|acronym|address|applet|area|article|aside|audio|b|base|basefont|bdi|bdo|big|blockquote|body|br|button|canvas|caption|cite|code|col|colgroup|content|data|datalist|dd|del|details|dfn|dir|dialog|div|dl|dt|element|em|embed|eventsource|fieldset|figure|figcaption|footer|form|frame|frameset|h[1-6]|head|header|hgroup|hr|html|i|iframe|img|input|ins|isindex|kbd|keygen|label|legend|li|link|main|map|mark|menu|meta|meter|nav|noframes|noscript|object|ol|optgroup|option|output|p|param|picture|pre|progress|q|rp|rt|rtc|s|samp|script|section|select|shadow|small|source|span|strike|strong|style|sub|summary|sup|svg|table|tbody|td|template|textarea|tfoot|th|thead|time|title|tr|track|tt|u|ul|var|video|wbr|xmp|circle|clipPath|defs|ellipse|filter|foreignObject|g|glyph|glyphRef|image|line|linearGradient|marker|mask|path|pattern|polygon|polyline|radialGradient|rect|stop|switch|symbol|text|textPath|tref|tspan|use)\bescape(?:{{unicode}}|\\[^\n\f\h])exponent(?:[eE]{{integer}})floatK(?x: [-+]? \d* (\.) \d+ {{exponent}}? | [-+]? \d+ {{exponent}} )font_relative_lengths(?i:em|ex|ch|rem)frequency_units (?i:Hz|kHz)functional_pseudo_classesY\b(dir|lang|matches|not|has|drop|nth-last-child|nth-child|nth-last-of-type|nth-of-type)\bident*(?:--{{nmchar}}+|-?{{nmstart}}{{nmchar}}*)integer (?:[-+]?\d+)nmchar"(?:[[-\w]{{nonascii}}]|{{escape}})nmstart&(?:[[_a-zA-Z]{{nonascii}}]|{{escape}})nonascii$[\p{L}\p{M}\p{S}\p{N}&&[[:^ascii:]]]property_names\b(?x)( display|width|background-color|height|position|font-family|font-weight | top|opacity|cursor|background-image|right|visibility|box-sizing | user-select|left|float|margin-left|margin-top|line-height | padding-left|z-index|margin-bottom|margin-right|margin | vertical-align|padding-top|white-space|border-radius|padding-bottom | padding-right|padding|bottom|clear|max-width|box-shadow|content | border-color|min-height|min-width|font-style|border-width | border-collapse|background-size|text-overflow|max-height|text-transform | text-shadow|text-indent|border-style|overflow-y|list-style-type | word-wrap|border-spacing|appearance|zoom|overflow-x|border-top-left-radius | border-bottom-left-radius|border-top-color|pointer-events | border-bottom-color|align-items|justify-content|letter-spacing | border-top-right-radius|border-bottom-right-radius|border-right-width | font-smoothing|border-bottom-width|border-right-color|direction | border-top-width|src|border-left-color|border-left-width | tap-highlight-color|table-layout|background-clip|word-break | transform-origin|resize|filter|backdrop-filter|backface-visibility|text-rendering | box-orient|transition-property|transition-duration|word-spacing | quotes|outline-offset|animation-timing-function|animation-duration | animation-name|transition-timing-function|border-bottom-style | border-bottom|transition-delay|transition|unicode-bidi|border-top-style | border-top|unicode-range|list-style-position|orphans|outline-width | line-clamp|order|flex-direction|box-pack|animation-fill-mode | outline-color|list-style-image|list-style|touch-action|flex-grow | border-left-style|border-left|animation-iteration-count | page-break-inside|box-flex|box-align|page-break-after|animation-delay | widows|border-right-style|border-right|flex-align|outline-style | outline|background-origin|animation-direction|fill-opacity | background-attachment|flex-wrap|transform-style|counter-increment | overflow-wrap|counter-reset|animation-play-state|animation | will-change|box-ordinal-group|image-rendering|mask-image|flex-flow | background-position-y|stroke-width|background-position-x|background-position | background-blend-mode|flex-shrink|flex-basis|flex-order|flex-item-align | flex-line-pack|flex-negative|flex-pack|flex-positive|flex-preferred-size | flex|user-drag|font-stretch|column-count|empty-cells|align-self | caption-side|mask-size|column-gap|mask-repeat|box-direction | font-feature-settings|mask-position|align-content|object-fit | columns|text-fill-color|clip-path|stop-color|font-kerning | page-break-before|stroke-dasharray|size|fill-rule|border-image-slice | column-width|break-inside|column-break-before|border-image-width | stroke-dashoffset|border-image-repeat|border-image-outset|line-break | stroke-linejoin|stroke-linecap|stroke-miterlimit|stroke-opacity | stroke|shape-rendering|border-image-source|border-image|border | tab-size|writing-mode|perspective-origin-y|perspective-origin-x | perspective-origin|perspective|text-align-last|text-align|clip-rule | clip|text-anchor|column-rule-color|box-decoration-break|column-fill | fill|column-rule-style|mix-blend-mode|text-emphasis-color | baseline-shift|dominant-baseline|page|alignment-baseline | column-rule-width|column-rule|break-after|font-variant-ligatures | transform-origin-y|transform-origin-x|transform|object-position | break-before|column-span|isolation|shape-outside|all | color-interpolation-filters|marker|marker-end|marker-start | marker-mid|color-rendering|color-interpolation|background-repeat-x | background-repeat-y|background-repeat|background|mask-type | flood-color|flood-opacity|text-orientation|mask-composite | text-emphasis-style|paint-order|lighting-color|shape-margin | text-emphasis-position|text-emphasis|shape-image-threshold | mask-clip|mask-origin|mask|font-variant-caps|font-variant-alternates | font-variant-east-asian|font-variant-numeric|font-variant-position | font-variant|font-size-adjust|font-size|font-language-override | font-display|font-synthesis|font|line-box-contain|text-justify | text-decoration-color|text-decoration-style|text-decoration-line | text-decoration|text-underline-position|grid-template-rows | grid-template-columns|grid-template-areas|grid-template|rotate|scale | translate|scroll-behavior|grid-column-start|grid-column-end | grid-column-gap|grid-row-start|grid-row-end|grid-auto-rows | grid-area|grid-auto-flow|grid-auto-columns|image-orientation | hyphens|overflow-scrolling|overflow|color-profile|kerning | nbsp-mode|color|image-resolution|grid-row-gap|grid-row|grid-column | blend-mode|azimuth|pause-after|pause-before|pause|pitch-range|pitch | text-height|system|negative|prefix|suffix|range|pad|fallback | additive-symbols|symbols|speak-as|speak|grid-gap )\bpseudo_elements(?x: (:{1,2})(?:before|after|first-line|first-letter) # CSS1 & CSS2 require : or :: | (::)(-(?:moz|ms|webkit)-)?(?:{{ident}}) # CSS3 requires :: )\bregular_pseudo_classes\b(active|any-link|blank|checked|current|default|defined|disabled|drop|empty|enabled|first|first-child|first-of-type|fullscreen|future|focus|focus-visible|focus-within|host|hover|indeterminate|in-range|invalid|last-child|last-of-type|left|link|local-link|only-child|only-of-type|optional|out-of-range|past|placeholder-shown|read-only|read-write|required|right|root|scope|target|target-within|user-invalid|valid|visited)\b(?![-])resolution_units(?i:dpi|dpcm|dppx)unicode\\\h{1,6}[ \t\n\f]?viewport_percentage_lengths(?i:vw|vh|vmin|vmax)Bx} GuJ8ƀlK#WX#9S33>F={H%rrEO}BNpNH!U53ݫYI+yuW^ϳ6'S$wagO9Sw%ϩSJ3{%eJpOp{@=c<܃pIml8$i+Z+GaYX  0 VZ]3jsJ$9Tܺ͡9I_[7"Pd9__7"Pƺ9t&(tՋe_3paFUkT Sf&nON|j{)7˧AwxID4y ^-pE}4S.hU'+[-,+*i K =`d i Y x^g"+[TuUJ[á3?/)nUdžxbm$$MrN5ɥprI.$%gJe X83%uaU?e2NURzb%3KY$]#Pg %pUPHխY#i@چYuϮFs@+\ڄyӍI IfyĽ@4EfQ= l/ť1H>_2 R lBOTIjIjyIjaR% 7$uS2~M) 2%n'$ו :+B7ԁV: TZ!@+~Kh%1kh%'[)zRo JW4-%"wNT%~WT(}$HUL\NT}}@E_?0PNTTCA@EK?R*{ Ӱ~t2ϏM#ekgQxi` /)'&êrR<SORStk4??$o| &k)^2$ y F<-~WDYkzzQHȟg*HAEd# ViI@xdJXxx>ˀ)WJK#EEq=? "$[njM ֑ uU^JtSh@ v06vJ:o؃[iM.%oYgil$ot5 w k}xJnw6 O0!+r$gS҅A(FyfMQ} @BU>^Bk,asϜl{0zio4}U ;GtVEQ;c Nkx 3Y 6ﲈOm$YTnѲ`aqʑ-˲~ _AkD c)-CQ_ B g `oEﱚ ~t~U ^==gN3x#a$?XS|'n '@B]ZU!Ln3Xjf$ؿeI8Rꔼ h3,(Fz(*&"e-8$S[=' sƼŗtAX|h\KY ?Vqd*a>l5͏=JV_?eɼ#A>7Zt--~x~ܭ`;й(]fkZgae =r TC]vӤy$k/M b]G=0n+UzxWb% N?ݓje0ix11dE#*ۏC%waU-ي5Ndѩ!}j.؟]˗K(N `jqm ɜ-3U>'pI 6MbĴ4OGRY b}x5Uw) {Igˬ]e6%*;CSVk W6A5 OׇAz-2> FᔢK!;M016a4 \\Z^V2msSYHc8謹`vն\HIS.WmLPݵjMϳii:&g۷w]K 闝?/?O⟏9sKK %Y'VC|yy?o9_+tyy_iQwqO}ކlvvUp~yB&Mo &1RD`XѺݢ]DnkbeO <%馛?m2 T^*{ʴ*ٺn:Cj dԘ!֑fEx[8[k 5W5ӫ5yuEO"_;]!:4㴒ӎ`V Wd5WAg ߫cvg1X#oN~ԂTRZNDٌr:ߣ/qSFSt%z( >h=}嶀hPgK m:/"o0_vzNsuVyV!og I89u5pc=p8ز\5u=psCs<|}qy9g g;7wІҜΙ򦋌H5- +b\loo>]0|%`ݹyQ]4t Wm`WCԮJ}w䚗]vqo[:zJ޹:Z)88ץOHk4 p: +(HLz$ڽ C Se4^,RX)~tIz=8k W۩β*Ih z HgMRPPG!QPC9[t94;\3\P#'mfxf&'Y_&ka;,$Y+tQI3.4lLiacRLaC%hGsW(ͲwPI]pWW0~f1f?P@sciS]c՗gda=Ua ,I3qz]9BXoz{i`9:o+( BFI`a#7:n-iVơ>ƕlU Yas|kX׈7$"OfdE=[MՒ.μKm>9УjZ)G=5јzqrQ7 KmJ;5ޛ}C+\j-:q (d+!AJYh|vӟ8 |wqp9fd8LM;*z.`s KG~Ky4p;:a9Y~R:%7'nrz^A$ 0D}^I銟Ga+#<~` ''B'At4z|bDh@F܈+.%R^kN[vQhJMlona/Fs\oZ8b+IK`Z9lFtyqm:y.YzaEeTZ|S +Sa>&/Ϸfib56{ӂ݀81Hh,VbX H~)mUl/ 6iU:{HfdHv#1.6>|sYyt|?-clF7W7s [q*.l[ T{98nz_Y#s>WU\-: ޫ^ٷDvQ^tjHQە{%z1>4\z/dw7V,?hd}s܁.Qd؍~WFg~vk;=Wݳi?y\1Mq+p X%Tt0g!J Q*"IsurlCOq.A4#]f$3~Wܥn} bCF)wdot| >^ާ$|,`{K, "8$igI8Į2hGi`s8^2,(95^@KRw P l(v\v{[QR4$5)c+Ӑt֖-zlY#p-Tx3u, 6ePa!i# U&UN%{'ȳ;͎sVwm3ZW J+l0Nɀ;r>)Vj9E>ݙ>Z)SҢҾtnT.0#9r *D;znљ茢3̰CQ[ܿP_TiSG H|U>i>I7cd:TwuA\/Fy]ftI8DlDܥtO5dMѯsxv ?ظI= sc?C*m7^I_w xcqxiiQ&MNbj1$vej8zΝ:}nOީCЩXABla4mvSqJ:/tw̝?~jlTAoW突&Zl:o# JBTF)+SHRW]MeK`LAP }+pV:Vq*+H%11z]@-RRĶTd}?H7;lyey+[G` 7RašfLf!!wA!ʼr|gTù}L4Bw%u+=O}@h1Xģ/lKވ^&$7NWƔ8 Yk0 ;7g4=HfcKG]ʗ:i화gRSIɽ]"&ClK/k.̦"Ytd&:LjNOCq͝6Zqv޹ʥN Ģ6aWuiL(8^\T)m@zdrA,!wӉ lϸLT=Ml@G/*sw& LwʲJxWFMR87Z]Q06Eĕy"vW=E QAvl 9w=WbEUNEWAEV}E*ҫA>0.0 Be-\T =6Pݓ,fWU%|GʦYCU6vNU+I#[UR.C[եL* {NuUJjÓ'uې$pƶ: m`CQ8J7*}^IKyQEi _ZO*X?V7ݪn5W2l#ڔi c 4n3{68OF[Fi,v:n?_> BWq9U(A6ŊVh dbscU=Y 3ĠM^=UL = T-$%j:ZI78T[GVՔd٭X1zawaǶq-[OSǴ}JAPR,dF-V͕>֟^=/4yH1 Ol'DCܝ`= [;$U*mUDKo~ӫō}i]>tpGo  &K}E_oOZ|9j^nTsmQZӉ6WWV}uzr5>M{3mC!ጁ3M, a;&VbI% |:'>סl'>^{~w˒<^!`3tPٝj4:0i q V}-HBda4M<ޕт0Z107BOCWUI(alBVRzIq 5o8^>Y [8'!Xᯝ~E3c>)r]jZ"Fsrc`ī%[%/;zb|@h Əc dy?yzv&OPb$0j胭{%g b0y|%4XKM2$ U@Ӳ>|[0V0ݮ\OYG;EL74(c l" wM(. `ՐȗW6Sx_ /b/⫢ }^:DA9;DNx^':M0C,`쇜H85`c+΍GE,}c ·9h̀ &*&`{H0d C$LM@dpDdB gFx.~*^ lY Ǻ `4BF-,&zWgQ*6+JOFHrm!'UkOg@aʶі^^$ YUk`p0=CgIW --nFVY# Q4]wֲZvkكgJNUlWƉkZ*3Lyr@B^>XI"R)^=Y[ؗÇXծ^cud "aolnȵ%PL<,=Z[1AY?q\˽O۱o";!b Yo6<~>@PqO0 sEW#CygVepe?v^_"GD9GxH8K/Tq#q~#^\E#ܦv8(0*~B'U'Y1tCG,@:`d@:@T8i2:]> 4w[ Όk5Si1%{nkIby.sڀep{ڐ6zt7!{[0;hf̒SƐ}\YmM-A>fQ+mZ !{a7pհp͈r DI!sf,pfnYȻ 0t{D4O46Kc>^z;mՕ۰oQU7@%jQLY[k}!<('A΢:tAN)J S(\!^'oF-b4%[|syxK:!n͐@fm&?uf~aZڐuxI0nf_YYMq'~\,a볝/ ;H  [cܙd~@Y `O%r^iÈh E> 2Tm峀 /ZR#Grs./eX hþ=QvFu k!?l0kjP<whPC'adf;֞ܺ"Ҏ㾋CG-,'h2f;GY7U(#S@4;50u8)=Ƌ31\OWKJ-N;m~H/,ȷuR1ujQ}K S廔p7[`A1EjMwld{8AuSTc vH5b\3qMS t u%\p*$ aWq0u&50g^G^/]|..-?uرcc#?}}wI?@8?m6<=`QPlhN,SϽt6vb\^fV's3V=G"r k;#H|tfH&ѽ mm4f eՅb}og\X> z Bt%} XT#g `` Vs9VlfBl'nM+ٯR[ڙԁue)kC_`@<)-97eōv| ^.}p- M8/h@|Nn7C"n u@4z5F~PwErbh!{!sY)o,śU2.ffe⮧dr!BTِ0 b3$l{ka0˃+Al^VÐlU/.X]7zVB^=#K;2IO)ۇC ,U[:? vU_@ taa$=Ynv AgT[pY)z'Uy]:Nv8XN~m+\/LVL^]<6F hlHZ$/iy,I;iNq$_\ Au5YE0m8ya% 4)lQ4j儦P}`tvQ1Jw$mIrMe\b0YYL u bmG<Spa$+u7#jdYRΐ$59 pv6h9{K.w|[>aŌ$bEx.>cq 8j<3FhԣBl&fDl"3S ̳q|ꅚIl6fAJ/bw1EC!z )ORslƠz`iKj>XbT 66J򠕪"[ /\waYY,53ij(?L!OKa O(+*#RmgߡzS}!< ~[[W9 +Ҽ8ՖOwO𩾢ly0G\:3 }󈂗SA4f; + d_ I(r"kTk< ͆]|sD÷o}a_sx."uޔuZ΁n6u@҂~14z@[suM7ALӮ *f<^5[TVՊT"O+"7+"g*"1k~i *=;5\{>Y^$R2{)އ]n;2ߣB~QiirB(>gaȷ;gՕ1ށM,x5;HBU`]zoΘi¹lI[%7`  ƶ0({x~"czzE7U^E;*"*W>2vF J;3cJ#e #PK|MgݙuwfݝYwgݙuwfݝYwgݙuwfݝYwgݙuwfݝYwgݙuܗz9dQ=y0*…%-~찏a D;yM5*%2 rFъvǫpvw )[F%}e+ds|FhA5.Z&ϧmii//ǰmtJ`))X@C>pd*Ġ* ΤD(vr͂-D94;ܢCpe3cd߃LuvP2w,>4 VqUbl YZ'Z‡Ej a6oA(S01G3KI#aK? Jy|KHACO8 1 x^d edi $`#vjGPQ8kHI&>ۃHA5r0yװL$O#   ?uNW ` ))% F㩝 Ho 5xYћ/a0YX ~ey la },i(M%m3f O0+{Mj_>hž&ci'Z #h. ) ai\{ɪ^L~$$U$vQjEE R*e{>L绪[U1W7\`Ib!NHk `]aeD$It&R r*WA#e–( !A$`V+Q8rTp=hohCKç[WZv Ƒ_JB[^V `Х/ o™@"?{]npv:EnjsPv&25'}^_ㄼ.IQMgӯ?*+f&콭U_b=k~*0~N]e?40gHnR`u`Zh{NHd\Hoz_UQַ,Z9.38ΰ"n#.X<^oja"_kfŋV`jkaFs3,/}ѿͮ}naobֈWοM.:AKx.LW.#R7{zam/g|ܪf9h3cv!ޕ5o?o b/.c8\f-RWfP߳N|+o߷;})؂`/)gˊpyT`{u  F:{4f+U=J̜%Pg.:KtjlԽ "Bu$q?`fd"o~I2*6)ʨM Z A&T =t÷U|FFOIޛA@A'?v5HM•:tAS>pG`=C^wK[:o_gԨ^LeяP^BbZe{ *"+rBnËg˚kL Ƣg =p'딟_Vs3jN)2jO)ҬVG܄.FZԃ ¿aF.zXa&=xQ~EUIU;38A^lð_ǽ 7oyҀZ=>v9 c%鬝~s0 sj`_th*S;]Z#6V;mV>m~YXw6vzԕ]"XxwY󱿎cz:Z"Q,lԼ<ō^ɑΏJW[yQٻ?#9}n s1)r|H-q"nwqa?bq%$c&[VN50f՞s!}Ţەs$q+guV(XFi֨)<scQsՌ[4:,Qښ7n6  [OԮ_^:[ b6LLVMll source.llvm.xڝVK6M>\ EE:HCK"G6k! ~v6`rFqf͘κ~/cfR"|uY?L^.;>V*Ob)L(-j볓wNM#ajhXoeܓ]v-{vs~y?ljGg&X~ߞ']O8?"2 ginc#󦖼RzΜG^7{YGG9E5e*PJ6V9bC8Ece e\Ӏ(ԥ Z8(BZIȎtoR{oZݚ-EGALMQ]a \3kB(HycDo-P.y`bsnBU)j1 'xno7 GHFvw l ڠYbu!ԲuÐky&<rg>;DDa 莺Gw*,p2go]pLIR][;vpR+ YP>jV ձW GyfȢ8uRZ UP&j6y ֥4 y9GMn:?'F5mlqGx}!NRx4)9`716aaZ*혖vYǁtbp| r-ArkRJA<*Oid-R3Ё>vQTdDB;46 |DzT]Ǟ(%ecS yѿ]5ɇ$e'x0i JoyRw潜W ~8/?_<wxCF(Wj> wn%ǧ4z?TRj4`zĚGi/[f%˗[?8B`!ߎ_~_xeaxėfx\[ėӻJĘ"~u`lxůɹSoCN|v}8>[Leanlean source.leanxXn5mhKiK۔"ԦmiD-OH ! f؃IvUWBBBK>BnP'xvwښ=d-Dz!bQ;jUqʹVMP" ߣ֧vjߵPmm>66gZՆ_7;!9{_hoZ9 .,eyk"ZY5v%Ŭ}ۘT;PcIy*5FW,Yjio|' bD[%˵{wCd` qˉ!Y<@[BB qOsPvȥ_³ȩCYgP_Ӝ~]2q}~h 9"D{FiX"FhPCFY7C;$rzDbw\vܾ߿=lPk;*?G֚^_nDmT%aBGx*,>X -IɦEBM*(JP?Fs "tI^/zߠhPs?IFx q6_/V.s ܲsmd-ksU[t!s{q9sƯ_|%iK ʭ%l-ڋUلwM{;ϟ=?uO;"F><|_/_`;p)%l)VZ.ZkPg$|Vb@/X1fG M>BIW!\]EҒ L+;㧃;nMpv{;ka/8N4Ųy gqcqm%=i}A*Hw ;7iYMFay4~~r99@+} SQIYv@4q":JXa%ga9ח Մ!aPh{NVTIbRsH5_ F9UDToptR j1 c_q+GTj~q̳l-i vqqTnwuws7ծ5r6]uKKQЫXmuӟ޷Wv5r~W;F\*ƳW?m.jD_[ LiveScriptls Slakefilels.erbsource.livescript ^#!.*\blsx[r\'Ne@iQحg8$L Z%`D{ }Y@@$L9#,={Yӥw,mOXtȵN9l0G<1, s/faMqG,NOYhy9m%m,LZ7R c.1ԏ(ǼZ?Z?1׏,F%#po#`ʷovV׿IwҶ,\riAv}FwYcAĞ`ɱU(Ǿ[ÆfCexE՝~ l-˦pFK2G%J{! r*>+)dzdz꟤%/ktZOғe֖sS VZYsKV35M`u;۬xM`[lɯRH[TZ?]nXZ>6._;狸t_Kh{]8kzw`V+(`E,⚤? =y'ϑ<䃹?g*Hh*G%ʼT~S?qD5XXNZan$z,ūQsmL 3=Cr,NYZ2l1gΜyA`W>~Et/Nk 9s|ȐV CkfZ"o-Mޢ y$T Ц c"˥9hˋW2ϥŖ..݊" 3l$w'+U$B06fhm6~xm 0\'5-a.ToraE$W*ƫ2[3kiKy3Xoah=lo0ͧiH"qi4k}"E5TSF\S!v^*t(,EQ*'M ⨣Ea-\M&3rpCp9 hm3s$9^sov)Av~)   | @ARcqTw=:qwGˢM4kQ/Ę37b k 41=-3y\ND3@ڵ Y.|oM~=NrEU1505rs\DžcSt>f7Zo59"-jb6[|Tlͮ~z]%@nꊩv$@@T `7W5'fkٱx9N!B.D&ڻfp}!vRtEžZ"]pG4opF%"zG̒uF> --zm a^iJ47R炊U=RHFdk5|WԅL *=x+ HI6ЩVP+p0:b*&mUM)Z3(Pt**MD+5i[U乘VHؼތ9tBsLT[M؄{(g- GjZj^n:OF[4*EOx >( &&#~ar|guBU%Da,ZUT)ɷZ^DV.]=9SΎ8).t*':p7tpϜ Dft"]\JpdN߮?s(*8WR„#y+q!кVmNNRL0TRڿR#j&([&,ّH"O!%iHVEfIfi(s|0tsƺ2mm[Mj" vmTVE/.e\Q=47Op s/aMvrWwUy=@@}R\Jf+E^bN]+$H!%vL1 *cfa|≒2|5"wu}x p( 1(Gn1[1G(U)*)Q=nXFCX>v,h԰1k+.UGAo@\\UƜj;#Aːq0RPh<1AP~b7 ]֞3 #g@SFh!]E菕@"UMgg~PhFa`'b_C$v~FX+)|"UCKDc'Pd.SL*$rT[( =!q\kcuƘG(cRqqg/P !,9cK?6 .jA"\Z1l2  阖=%-XKlE%O,:L*Ȑ#>m.]0pxx>"J Y`ѓ%qB(V]渔+Ph )1m~n(wvYLmb1k-N2wdH@< RFĆ10XM+ ;m8IO4(J \rF&EH+Rn誡*H`aU S!96Sn0'ɭLRKQ0!˕ U߀W佊hHS8RN/NmIҞ/o| ]B7ӔSKNTHwE۽j".:iF#>ҔQ-iQu.e&ɹźOd}yUq/dP>? CBxp9#3ق$Ҧ\1rQZO*"ѢD c BQ?JXW9(]ALd#ӆ 5u 򶎮a߄M#`ox>w5V9q z!dZCxwzb4> c+XSX! =dQ&CRɄ]A 8Q v[%Ϛܨ 1&#-TIC~^LpUw]9+IBR+!?Rj7WXX&CTBj6$CmLhLU8Rߕ-͛K5) e.L~Q8*pbY-pManpageman source.mancommand_line_option(--?[A-Za-z0-9][_A-Za-z0-9-]*)section_heading ^(?!#)\S.*$xYOsDM'mS$M$V-4x 0hԥHG7d$i8p )8p/ծw-+߾{o{YSi;v4j7s Iy0cbɋ1f\ 1`1s\0bL4&">1 $(6Ҳl8rlqILߤ2t=1]|<,ao[  !{{vv(,V9݃u\w*f/ڦeCy目o0.0{ދCf?L3 EyY)L/1q:v~z 3.0#.3VdpW@WڍHk 5>I.2t_gvl:sx{Xl? jt}I+J^[469"'-^3?Xкa)kzA]U]~2|cw)nPyiĆ8Eœ#O(6LJL,QTKeU0 G\$1!QPպk6"S݌rpEhA2ysiۊQ tRdYbmO 2bնPDHġ{QY+H{(Ur]b:nSu:5}ǍJI }G V5VՂhaj(gDPNl=bNUY}#ϧ=)Jn/WJ\Kksn;)Lv>'2k|Q٬Q_fP6˃~;ৃ_??;dCĉ0YFKJ6J< eelT.T 9Ƀ[*^O!PB㜶y!ʾ6o>|ucn?|";aĎqxĎ'%Ĩ̟iI?0LrڮLe3!qJ.b.Yvm",56N}%|C[[./a^6[{EW*6$ِᙗwXzC\ۯ湶 %7 tl,vB:NF,81+q\߅@lZ<Y c̑ HR8*JjĎʑO0i8OzA.hU9)/x9\c1Վ=ry^ó<ӰIx3ME7r'5o#UbۈQV""'d"(i|MfێN9\Z迚żԩ%>^J2{VߦT<<:ksճu$; / /6Ǚ,˧~iVG _vMediawikerPaneltext.html.mediawikivxYnF%?J;-RS71GtR!(klPJZ?SߣsgI.]q)*H`7!&gfg曙Yo$׭oyw`Ż|h4nj)Ѵ4?2фA7*27*27+2",:tqcqęη/ nf_Cb}-0c#uW.̾ ߊЋ5g qf1{jX:v1 .b>5 Ű7sf(gmUQd< ;QE )Z['$rЈD6 "şVp6SFiD9F&x1=?R'}KX_؎+x4G;64s˞Pvtdf{Gb)MA[ ]c{$!Z'(9Drfcr7tޠ`rrI$@ExIν)p `_l " 9]'+Hyo ߊj'fSyK_]؝zp^)0Y UKI|mMYȵ|6Nd{gV,նVYU4>)%~E+Ts%Z6 +wA&`{CuklE}hYk -IrL4KBW[_74b2El=٤|+l0v Z&q2 |W%Vh//-m]Z-$Zl v+[W,2TcCRZDMjVHcr[HٚF:ZTjqȲolesL" \鼞CA_4>!nʬRV9Y Q8E6.T]Ώx@?g/z'Z=>0Nڕh9 xhZє5/TX97)1cڼ e|6 ݣ {k]ӶAI]/KF"}?tho~E;1YFNL~u2;UM{w+d(ݾ ӗq<m먏 逩]Myϝw|/9O[:&h=Mz~Y2^ӰNGek*:Z  dq_loV%t!ll:im}|:7ђQ6e3CSy X/{QHKi '*^+$M<)Nd4`&Wyqu(E.B8z n0c`HU0⪫R+<8, Rbz^!d6U,\?;?;~a{ZR'qL#==7gIE^ŤׯmI{j"ˋ&L:$0cQ#9jFڜ>9`{+Zv+ 1\˵7c--|/ۈqW}Q2C@{|̀M΀/xQt6^g1&*& $A6u0 MediaWiki mediawiki wikipediawikitext.html.mediawikisrc_tag_after_lang"((")((\s+[^\"]+(="[^\"]+")?)*)(>))src_tag_before_lang/((<)(source|syntaxhighlight)[ \t]+(lang)(=)("))x]wHw7y0 b<$nf x06_06-I <'$dsHVYgsKde+RURURu#Vխ߽Uu+w}\G526Ƴ~E]Db&1+h8*&:Ct !!"z #R$9zQ9z"crDb'rHc9z"O_'ԁTDNu\sJrD'=Q:|#GO=QoԁTW:d:'pV?*GO=QrD.u(GOA'JgL*Dƥ'h^"'`f 9)r9A{fV3/'Q1DIŕ$J"&Qa_K$r!$J"(s}MizY]_3L5@hp(S)kJ"1 mϜVaI7B@o@Nx3-5MřP>]0){ !YLC(!v[LClZLCk&Vn6b"R;xn^i6-BK A~ s. "HbH|CD?דDe b"E b"% b"GD2%HI*AL$TH7 b"J]mn""""NaHxJYK3L$q5ѫOQǎ#k#:24!0Y=d`zpp5փi30D .9 (c7+^lʆ-ueI-.9-LRhiqh;&js9 đsx;⟵5[knZ8$.lj-guknne/U  <ĕ%]c}13:f U鴚rK%s7|A/Q!,k?fֳ͜ T&WDN;еa. hoYbں]uƔB+Nb- 'wkʎ88#k;8ɦTtuPܚL?ƌ JP[ferszК\!BE J~Kio T,:㕕qesV-c sPPSd\~X^2W^`{[1TPT}xKYs;4vr݃rY 3R^^S@&3w<^hN64g:^!Vc # "vOlh9i➋ƽn9}5OU*# c@A7*l>@=M?\uӄ8=n `Oֆl4D n~ݶӄSz' &'{M: Ix2jv|-mcf*𧢇ז6iBE[P]cմ`*_E6mͩiSަ  7 Ĥl7|fp'4Q6zl59؅JLtowVd&ZI3X5H*9i?:i8lTq??nm{_`&]/Fw4W1 \ &j44g)yM^',5 rg}4xݬ#=9Um GNo:P8pDcr:oo*"Xsܨ#6>~eƥ7voǓϿx ??#ĥ7~?|O`?a3> _[YBtGL[D׭OYAT:\q38C+l8> J"jg,(2 ANp Ud"ʅСȰ߲ҩ {y`f{yٻsBGp}#hBa\:|K/% MʠeSF =շCbBGp6ɜSV!'9Ql:Orz:Sϝt.3PgPd:cgw/n$Kor|'>lN矃~e W:+*J2<*rK[l^U JeZ.W<$dĤcaqs烞UF^b 0s1kV>U|&OW״7ѺJXTx=L\Cg{ UMb9AY1PF82Y3>B+7jo_աz$<, yˋSF*ǃ!78_TVF73H^gnxd=QjњYX75/ RGhh=DC*[{雞~@J&IW1J`m'p|,bP2w6 ƃ[13,.0p3@_:0g).yNiޙpAhƙnGV2c#JFq֪ _=tAǿV- HSll55 ^ϴLE 鬂6sYx2&uߣԕATP^ E‘h x0hM 55ǡP%4ѓD_27G-!k[@0k Q\>2<,9@n561дuՅdpP`$d}ۮ9$@2NK ѺK-RGj;@A #\hPup"=SZdGGgyjC,r4AvTe?+4"48Y >°(4S¦6Qf>m=,.'5 fc@ww4vaP mLSgh2C8GZ *yR S ̚m2^ƵUY^S3?H 6hv{[D֚H3V]GG-;: (kzgnI y{Gtxyr}~VA= '53,:4bIr[ׇ-1 Q%9wW}Ӑpj5C dylOmw Q$? Ӌ~b }>c}>cT~ouݷ_P~~|Pi8ʬY%"6>MT|b'w~`]ö:w[߱ w_t}#ܷ V|xM^8f8ي"{ѓVR"m5DF\ZU~_L8QmNTwՄIAnWȰx>c\@Įߕ1%0Ejgzh/WtuR"ج\P,z:Oy[gX]@Sf8jթMNqi6TºUMo6S?>;*c5e\TSB+N(<*hKq%Ȍ lhkTHԒ bA,GR n'*xi60TO,.<10%qmTHTO=(z)fTl'>yRDH&g<{Mzx:Mi`5n֬4ְ)~N+A?joͰө?!㵵:[:yu HlU:_[zjlpoU_VTy8aI`F^*\t=bbӝ )w{}najL0&<;yc@2&?j'! ־=AOij/JZ1:#CUF9欠>''2妪t7znq7p1GZ2 +mrn _AH2hH/е:\AN/jհ+\]y]\ruBusuuq" ٭+ }6Z|A7/GkّqRT^.𐠣^.gޚ )w8yB+rΡ]&,1y},^œ֔H (Р*ȃs(L f"~m›1$2aA:0 [ޑBB}VFtHsHS`紤I7!k[cDOQ#;*ʏ%;;CӋ&є'2m8:fKcM W.&Ooґ;Q&P" gVeR|x hٵbs!tQku򇯢(i6Xz. @?OUaGI7ϗuV̄KȵlK37Af]G!o!cGnwDC%w \10+T+Պ*bud)G9gt.eK]"%$K\"1XJ!yu4!(默5<$A؈@E ;#Э-5KH+e{oE<u>.s_ГZuK>gzI?.C}+Puy9Ļ;{F$"#I5f 0gt PyeHc:!)RI^)A.uxf%!Sց9f7[~rӿ_#;>_;{ix$T/!x;<^K3W+՗K(L]UT__Ўe#]Uzw{ {޲U*UP龹2g /,-L{w%xI<آ7_z]wssSL aIpy\*/:T-M{KEp[fgJ@ —f%'m͗\lY(P{2ܾ,^%_fXy"S[h/1x#~Im3x' o+Sَd` Z!*ホOd3\ k.OC̞&n-W[(, /n-\ Yt eHd÷C^4 _BC6 LTKEp"ƽۊ?8$T~ -ى,VPO#z` .lix_e(8wABQ.aMemInfomeminfosource.meminfoxڅPK 0MZѝ{]h+x%hԠI M-^gġ >ä)pUiLG1·18%㠑B3m㎒i9X?xE 2ەm"϶!7A%䳵ĬJ6/6.f)sA7}g0?NSISnsinshbnsibnshnsdinc source.nsis definitions\$\{[\!\w\.:\^-]+\}dollar\${2}\w*escaped\$\\.language\$\([\!\w\.:\^-]+\)uservar \$\w[\w\.]*variable(?i)\$(\{__DATE__\}|\{__FILE__\}|\{__FILEDIR__\}|\{__LINE__\}|\{__TIME__\}|\{__TIMESTAMP__\}|ADMINTOOLS|APPDATA|CDBURN_AREA|CMDLINE|COMMONFILES|COOKIES|DESKTOP|DOCUMENTS|EXEDIR|EXEFILE|EXEPATH|FAVORITES|FONTS|HISTORY|HWNDPARENT|INSTDIR|INTERNET_CACHE|LANGUAGE|LOCALAPPDATA|MUSIC|NETHOOD|NSIS_MAX_STRLEN|NSIS_VERSION|NSISDIR|OUTDIR|PICTURES|PLUGINSDIR|PRINTHOOD|PROFILE|PROGRAMFILES(32|64)?|QUICKLAUNCH|RECENT|RESOURCES_LOCALIZED|RESOURCES|SENDTO|SMPROGRAMS|SMSTARTUP|STARTMENU|SYSDIR|TEMP|TEMPLATES|VIDEOS|WINDIR)xZrƑ_ŗ>RqJ@ol֎c "P(1$Qd =:!ެSIݘi{_TNf*ӇwKGu<|>}PwT{*oAo6o2;CZ -1xi3 uw4rZh ? 17\}22 @ vzV!CX~zc1p YL1S;>W۵o\oɩxH|˕u'҃Ads I {XcIo:C?q: FMTg4i7#p?R@A^0sBg?Bh,vtdc^m65$LUaz߽6=d|MiO3/}eEWˆC:?{[CY8;ׁux8[MՊY3B-6jQu;dt`uo`9k/*]'N;ou_fz$_N(^h,/y?yF B?md/J#-ڸ$4<^_d;(0KK4yŌ@:` s?zoöiɲq \OXy׳:a8RS²s)l}%e=]0z’alVf…Ѓ^`V5clB\![k(L*Uq2K,Tȅ(Ex~͔TL@ۼ-8 k0.54n8 NA5J4 s,¨W%kI0|Ə\<㒵_vSӨ)q^r`i4Lb ;&2Z3XoΊ(FJmCfg0!P.H ^6tVI2˥M50X+iIYFXp+ ~Vf j C5k͕GXsqQ30 `b`:?a8qZ@x|eĊgTbag Z!{fŔp˧Oպ&;++2W%4Q -I5XZXPa0C4bkMBp; ~a2^k^8!,輖"[5b(L9͌&YCtAM~`,j^z `T%ŬĜlJX7pLiz&lc<f`dVϨVk ?(NȐ$@M ǮgԈrzcc)_WX!Ź6C*fT*O7"q\̖|{4"Q0Zi ^-m^~+.s:DhY񬞨9ZS.ٕ`^HXZƏb!!kLLCpdI0/{8“$H24ވ*e$;\rLHl6xؚp&#eԐ/56BXtabY1. Dn׆b q*|Ȃ;*zd#BYY"ĜU'`piK1=V4/eO~y]mpw{̴>R53pR.YB{Ҟy) ZC\^RP-LOؼ ,Mx9$ejR ipIɖkBIX)ؕX2y?3za_>{@ e'YkWu@.UolR#\5LP|`*zݥáhv(vc?QCYKJ)>8uzxMȤS>*2zS}u6MORΩ(~ LQ6AB>;H Pp=G-Y$ OFz: 뾄*)`ߴQij qpWm48 auma'YPy~rRڤO'(pj؀!x*ePk @`X>a`ss'AI9ƢPlFX'xM'MjOFQF]n\ʢf~[ x}Gj0_{?u 4>A]?ئ .^%w'fa˞*N P` ^c^}׶Ayhr)-p#6=m!tqJ, a)Iy cfe&Wsj!sFݫr&8nD_˗:_ w|j{\#}m+׭9;' ̺^#[z LSy1% &k.[!kd^pR+u.&_ 5uO>pPԅU^No.הCBylR\XUz`7>܂>mv}YsJ#(zm@{?O|==qgFM@JKFx#b.\lك:R32 b@fx"ԐJ-XYL75G1iɠ7T$+ !Tޫ;dBuz,~O !wTfP6X[W=NUzZIa L7w,C뗚͐lQV=W{Po~lj?8r?|?|=>ӼLj6<2a<F*z7~r(V@nR]I'=A#?[V 7Kiܥ[U?P *߾]WtC' %Y_1n|oƿbW\5wz(d>z7Mv:դ 5 |;k3}" A3\mnginxconf.erbconf nginx.conf mime.typesfastcgi_params scgi_params uwsgi_params source.nginxjx\o⼜y9q,d,vn hEQ-PZe˻[hoY:g~ob!|.o圓#spHpH\o)Ni}^_n=`=]p0L:s{y}`z``T0`L0n9L;+r$I{$IZu&i+&i7L*o9U[EH\s0I`Vy$1`Vy$IZCG&i vH6;'r$r$r$Ir0IL*$;~vyqoHH Ira{..i;O,Ѻo9'EXsN<b_?׭?EE0qZbNQZA:ӳ5xߚZu'E=%yb 4,ʠq:(NcV#*W-JJJJ쓉ִ+ ն[_m(ACl!†[gCn f"Ah!wI gZ pA2=sen56 5jT@.` ͛)"9Y<+T-B4{qzې*]7 ] n)N+Vc?1ls Wq&e*s8A4 v,!""$"BcNJ:U +OfX41,qnYBӸC#b$&4%ۏ0;fpe޶,"mK q(+i ӫ+~`V;?b`"t$çcQ>BzHպqZ&EsU^Iq]ưH[b6NpQJJj)J[6bεghǸ 8 U8)Ed8Qb6-$ G3?N٤qr?79 #^igA11$pYg*G*N8"ӭ׋8=G>no @ QUDݿ([Hcτ.w睻``= ##YuL$Y<]TRݭ[ >S Ѽ }!h|X#:_4CpQ116=dnH/4@8!̱JVƤYبբP$At#bS6@/U[wܒݪ׬M[h:QP0o_S|!6DuP,~d1x)+/ʒAFPbTl&b`6 hnD`+e%Rhg|"$Q[u.59F NP373 8?r $A3?U"g*҇xQ*J(~'^a/B) uC/M/ 袠PW-:kQCq+5FX!dy^nd^VFv'0 (wɩzj+c"UTJʩ3[ (>ZQ鵼Vtƨ߷A3x?rڻ`L oiP{+cU N,tEXL{Ac,7Ʀԑ M >Qx Q˄̄!g@~CA E ș2Uٟ'sh6ag=o{R׃v]zz㹄z9)b'4HDONREፈ"*Ґ"V4嬈"M*b'H[f4HcN$| *PD&"'Қ{"s'2]"*DGD&썛;-A>4fJy~ ޭG4W ]B畘.@!Zb:xzvc00Sǜ*! MUKzYI=)KM؜w1&.uqkm,S"X|JHQDm9 ]o b~Hz &X7 XG 3#LVgD]} E/e\|~ݺ4tn#"[]NNY]9Itu s:FPNrWWS,_ǩ&ogGRm nOg}~ac8!rHuQ @71#'&1רo$m4zJvZa-c-\|Ї)'e?Ȏ5x~%t A 2o"Pu(eӊrnJ+ S7sw:Q :j?țe[6O‘xqT8f`q`w9ۃ!` w݆cNjQW3PL_+qSAG}q`!G/N~3_G<ǿ9 `0l GKzʞ 4mߌCX|z{LPŸA=7gU񚒇4Z"?w+qR ~p-WK Aۨ>Syy UYՠ<ߏm5rl-ׂ]o 8A:xENimnimnimsnimble source.nimxڵY_@v +% N6. !:99ͩqV4h^KJ}ܑ<5,{{[lܣz!ይUBsi| Mh{QigM 4ћոsFoU|ޮXӜDӼSMsWӜiUa̕|0A/׳ {XR!/ ,vy=wDh"K=ÁfÐE 9Qh0r$~KOG~tɞ5 zf8\PvIڈcA!]ĴI$qyt{B`IuVC&-۴5z"˩ J)~"Y+!s2R,Jl::?&\2x#\E%' 2r9/.7J(H :A4pT$QW s!`d4KH1jڦAuhژLݪh^,qx>%­bւ*c舶6G-sޤჸpp>6ӫr,6Iܴơ6? qy$ΝM 4 gM |z[ #7U0,ps<,]XE|4H'[W0 yz 'b+($sn]A|3Uӄ &횥9`Snxv]jvIn$j~'ݮw„]T͋5c-R+G:$>|v{+*Km*J ٷ)f^LwޞӴ6&=E̕\fS zB,HUMZ_R,!Q|e],AψU#; ̚kF^T[-m#^5-:IZݶum-Uݏ}|=myZbwAtfUHzFF)4)wB RTD$ ~$@*rOAʱCR6[r طk9b۾QԸWGh>X".aS.L><4d.`}PBDW7+jDě4z\H A$ҮCBG  ]LgErBHZ@6|܀MiAw!*S,1PU+Advya{} %=@0rL#L}YqPjyW;C!-rFt Yr&s}O$0̃/G>[ÅM辶W/!A_T@D1F+0w!m /|F1H&l$BG0#>/^/5㱝._E ,+A[+{Cڬڋ-n!I66lWh3”AN6dz*>㟽>NAYM$׳O%}TTRC#fh hl5M[Y3ck*\,gfW3wMQ,ÔH1O]21~Wp,8R-o2m;XTD3ΔïOBc1F @f(6Yƺ1g('FP YyqVJS+۹x۶}iĥbda i]FXAZJ3#A12N r%d~Uk)OlNUXSpku,xC)rĢV! G yXBמ X܁Ť{0BTf@lHTp&SywM:W(a.LL#IQ?Nixnix source.nix x\ 67+ 1vqm6]]N8ƈIñ/~W-Y%KN{WH=n; |Cv_]wj< t[%94M5$E$%0#p1LgjX7m7[zPatDŎw()O r>ސB|3)O29WH1Ljc*$*;AIٞ>N qX)1l9Tega_]8Ja#Ä '[RGѥ9rAs?O)s#cw sTJ}HB낱r+ĻAx>A<)/a&%2ѓ)4-- eYI`. mvE:zfSmgDP ŐP;uSjq|ԱOTٜQyIyIS])zPGH<۔2"zFŽM7!ϓ%sĬH^$ *cd*&9!'adȨ#hlk':d\1$W{w5 A4yQ7$79VZͶ>k*AZdSD;CVJ;W9©pfx6qэ>t}%@dlD :ݫNFR Uvȱ;񛬅3sUNuvcך`W TdUkw*}5҃u r'mkĥ{d(Rz!9ej`7s1@NoԯXj6B^mv!;1ًviªؙP\"jїhu$::Ƴ,5*tF޵ iv0Пw"yW٫'LhggMؠxDD#?tLL|EJOᅲ\斘m6GI63FΔ %$ZrzO\& 8X*/˴ad]varD=ۜmNũ99?*Q"f8 Cp 4\BWߗB AC5pi$Z' szU*x̰098:u=~U:;8:x$Ҋϔ1tݷ[ YEV$]EMUQ WWth}u(79%u^Dp "u~_i}LgNG֦1fX1!Z&8]J jR ; su֓57ڍݎd$ev+0v=āk #?8&~fƌ4.g9a^kUÞFW]V3犬'oBeEgoaA,h^rU"L%\SdC,Gb:ԐX|MǾ2ڱ/j\28>wuiWu:Bg]#nD>{)k ēb ݥ'Tt=vND3*anNN$ D' hix=}hwϭi#`Q(UJ4\:zhFV"b:Tzff81Jk̇`1e8%ƙcdi5Н=kr"寑pt9?n6?,7I) . Rk }یAFV(Lo(%.ꕇ56īsҍ^m ]c,B_H5%t^K0]+JŃm*բZ]GPk懻~^B%PK8K3Rp3ñmJ<&\`T)0N=F}NeNm2g63bfVl 9+5ckF#lsѩ|_/2 ZQ gu:.1.诗ҮW ^et B?ر#lq-}1< 5ŮawllY؉Um5۱{iHXlF׾MO32Ɓ2aϟc|v Tڱ;C!PNfbǗ ]o祝?6BLt=vES6lmo4R߷}{q)9Ca"GԳ]#AґqِNvi%ty$T'AT?@qS`Sۨj4Rd dmLYv~B캶fONFq$zQC*u4mp`9ǔuHXjG]>%C;̈%0y3O"Qt>L"_堠ĚeGTUzs1c`d&iRxfd(>ϛB p.px)=T78dGb/?^ b`9v тi>ϡ'yw?xcAa;Btr:?A晄j:Sb*=U(&f&=؂rȘ{|jAC:?59`&m59+: OO?E~94;c^^2K)V$sb:LJ@;0kFf)fŮv8 ZFe)2wZuuײ%n+sQ{qDӨ  %Ӓu'Dtob)zKD]TGp҈֋`<Y\sʢ.Gw&̐mn3 / Cݳ{h*J1%"\LyY5k,Ϣn $t%͢NxRe畬F4Ehm 4 BB](h&Pn*aGYmlOQ;CQ;?F*DŽ`BpXOy94!"0焻b U(׌e!5w7Syt`xڢ-J HB1P?KqA̲eݮĪPE_VUztYqB%gOn5!!U sA=NQۉsbxH͝ՍuxP B Xy&k.Fͫ;+OYY Ðo$Z]w&4)\YքƗ&T5)˨t%[_'7KB'/K%wgpasswdpasswd source.passwd9xڵoO0ƁMfV"KA2+̶y۞jU[oxf6Z+"}DUQnÁѹќI)!H -X)RhdnwZ-b!^||e߰a&Qs7a$n';7'e>BnL51'ENǘƸa*xkQ׬O/=˽< B OT:h&A͝'qtפd~ HE_HF̀egRE1=w\WTl q ]/ PowerShellps1psm1psd1source.powershellUx\~Ʊ$;i[47',&DħUm3DJ)89-D0.TKG, Eb3;t;}3a;䅧Yjdge.num xɠ|/P5}b5͈9ǚvL{K: ޸ nvWgS@烆ojKlԕxoobi6!Josy;͟x_,Wom~jkMuo')tb<}'qlȊ,һB9$ϼn; =C~ʟ/,Ύ*R aA@Wß "ЌjM:Y6հ:G襠ay)(G-X$e5v *3AK P'.,.uMMĄ7!L?: M5;gotSؾbkQ şu۴񖷂`}D􉫔@ Jy@lGt=iɈz_)ՓZP|Y[\3;NZo4 k渶6m- \!~/U;$N{vd=bG=Z8EInq@N!_^KPJsq>,V5 }s9!hcl)r9/BNLr.(-%)B!LJm[Q["F'2ZarkUjfMD(z9$ CME+IgN/ ~?h 3y4(Gt_=k}Ks}dg mEzhwjl C\V*;e8=;0 6'Xnح55@UMcWZRT}KJ r*I.vsKT`2ȝSW?;qqvvLтz >[atI }mGquqk2cy,;}}tfG#HG²^2?|`Ϸ&m+GA~-iaGQW\ e T8.jҮ4*T|;vYYB07,Bz@2m|4AT.*D'XSO6h5ˌ{FQ@#M,(VMp_gAߺy^ewj꺯Jߤe:i+F,mubt^hzGU# Tf%+1W0?HtbԚ^Vkkb>0bCT(ɲTf Xthq0v# ɳ#tK=Y&tI' W!t v - :a# };(W[&YGt.\ t AR > < U=bLJXshHp+.} Cj[#H?CUc#ִ>t]Cv5b!ݰCOK(uӢp>hNJl)".]8b6Id yְGJ0P:kY J^et4hҀS{PnAi@Gy@{E6X? P"^ox^9\PNg /t`&(m}>nApl6'wB% SO멂C FL5<`I_510yMv ݿ}C'7[wus\g d 67tWٮg3Q]QbmEVl-[*-) n |+)KI4f6N !v50Qg+|H}(ʔ6AP<|07 M:!7=Z?R#ZJSKI4sO]Y.ֵzhv$'.uoM2?$KY멞.n&Zvh..e7x;tdbdL]"Ӊ%L];~cg`fR|/rqG. R:-O k173C6({Te p;ZW3T>(Cp?Fl]=c]L ) = 6D8R$[UW'dXRa(Xh wIAJ*25&G%R*x2]sTABnͮjEP`}_aa@oA$PKP_,dt H;>b84g(Q(F@3=4fFͮvX cq60uzaVIɎ aSQ,kQe#C`Y*?sْ#=sg.'^:*0TN`-0}߃}NPfԘߐ}]bMSb{,ZyCWw+ŕBDԣ5 g/5SF=ֶiͣ6=LefauV~a9dgS9\i@ RtAO)E 2\tvퟍ41ىL[6},\Ngn̴o,XRڬZm]h^_:&f RGNp 1\&L;69>>wN?fΒ6wGM0vš|{yxߒ|yBrCՉtQ1:v|Ii `Vcv/}X ")b[j)5l6^%#δ/ r4qU61)a9tK2;|O%+R,uprS),+1 \+LM{>$ODXrKZ,1u:EU =gw @Q0˪ay.)IbKf5 vC׎0KZ׉Nukd$CYX#3:C\X%"7faRXrR@wVm&|a)Y0;a .?*iLI5S<#\?'m 68 Q>L+Xj H2͈V ?BV3>!|Aִ]>گn R$yšr~stDfIY `X Xݷ+ LprA`|C?Q+ >H}.L0>TOxk.ȒjyG 8ET?2XHS6xSAy-5QA.ՙ]-;&TRI)?oY̋˨e.Wtg:^9ۛNZspyޒƫ37Sg]81_xqfX݄Ey(M頣P[R%09?7.#Ο&,#X\֟("uN ~-{,n^eGo)՟U0u@u0@fm2Nmbw}̐Ɗ#p Protocol Bufferproto protodevel source.proto'^(syntax)\s*(=)\s*("proto\d")\s*(;)\s*$ident\b([A-Za-z][A-Za-z0-9_]*)\b x[y6h)Xv[.BЌh۵-uXzl 0S8#='K>k}Ǐh)lQCNEi9V4G9Q<>0.{T8d<< Ge `gd'yG'BN8K/"N0*i4Ub* 2PW}V!#D3gB [2؞.7j"EJߢqIwNO@/9њv~mHPI&l_=+:,SGTR}dX؝~  I<0>D6q1$ ')gi$) M x'!LP{_Kv0H :%V4۟> }ӑG#&qfhn m2|AO>:WyLsO`2wlIQ{,T: #NS/*r'RC?by򔋠/cMO1f0I>$E {V6KHIlpw6[x&X:l _'[_oxF@Hgab98 n2f@<e[A lU>=GEmiIk·U,sNݣo7]qE4u*ܫuG,*$J7C}C6[KL\&ZW{{# ۬bSq =)I rWk?:y;PjܲɂQbK)0.$vB4m74m=Mۥ֎tFr%rZU隦0#Y"wS*UKdfa j)Jk5͚$L+VM.G;W^oR`E@<@W|pz*2HZQӆ5EVz8U8}u~nDrM7ErZfoԨNg`vjR!S`Cywa[d\"7,TJN4ا G5?mX/  w{gOKZ@>1^QVbajS$kt QօM~%UA>lԠu^caϒzͼx˓ b->pF?nq'lR󈜕ݐ)hmX+E) l#cmRzVch*ݎY N^\eگ7p%xnKKqIQO6ЃH%7kۮ7g~G?̟l]+E|ǰ5%5;N>ܶPT] RqrɊ \V Z5cRM椤1{[jd]ᚕ>Ȝm]li{vaY*Në&:Dr[C+7=v9,w} [LH2'1Ƚmt⦪K QD9-,wR\o׺x#=kmYl^kDf*0դx]TT`L>RB1RŅ>p ūOv &nJ.|Y\u^:^卲q1iProtocol Buffer (TEXT)pb.txt proto.texttextpbpbtxtprototxt text.prototxtexp(?i:e(?:\+|-)?{{integer}}) field_name\b([A-Za-z][A-Za-z0-9_]*)\binteger(?:\+|-)?(?:0|[1-9]\d*) stringEscape7(?:\\(?:['"\\/abfnrtv?]|[0-9]{3}|(?i:u|x)[0-9A-Fa-f]+))xXos6'6mӵYlYiӻq`eun^f| D4{NH;ތ"=y/yA?S`.SDxbFlfVF$@xd9~tAkyyb;+ g[7,/l_4,mKղaֻ-[uV:74뼟;Yd }[% f~L C85"x$Py&xn D8+2$˔DP5zTqLj|n6qO|UQE">㶩9dVaD tFܕ #]%cVF@ ˁ fLaI%E{ΊyJjz;u{1qaa7UQ1C^jźs|X1ۃ@4'kxԡkSm Jc물K \Hh`% Ðښ *4r [91Lv@!T2 H6lsC:NJ6*omo/}.PE> ;|LoL;p\zs^O;I#aa$ǐa_c0b pβaz @G3"@a :zR8g*GThUVYyVѾȶY?!kd[^u,u~M ' cӇN %a[Y+XsXi:rG/}8&O^*U٬+N:̤.ND0-y^[J^xqeoX 2)hA؍/Yպ;i^h^#a9K m$75xncIVU|O}O7tڻyD׉ku>?ڵOx|iIHڸϺA~Hj.% "6fVWHU꣊ڬP]]6?diZ̓)ăSęt`2Qhځ!O hOzqF`{ϥsHm{NkQN9'Nz7)qH૪xz>(YtCbu ɤRUBc40#1'eݰM- 7z3h`8^rf X!jNyHzBH~%YqG)rkċ6dɾ+.hPuppetppepp source.puppetxXYSF]b|--Yٹʕ%UyLނ֬4ZThGfx˟ߦGfZ`@N<dlÃc{šK㷧sg\Rt4_qkNsIM LM-\ו (a< |h vI{W켖U;q\9zn6ˏZjBA7cRj;a aA⣠[RߋԾw[8!zG0x(Z>$8|]WYp{xY(X{(4%xl&P˂W'z2Nϻsvs&NHO=gs*H*#}"HSAb[Oqgd_ Vυ .8|~]H|Ojh#s(#Hǝj<6Nk\ ŮO ЈKpL̼MRpr6Ϸ%Xjr`fMWmDU-E*w6>Eth*>W_7%{8tH7'fuR?DSl|u\Vv+@ǗBI[Po3(j`4v5}+Ual]9Vae]%6 mR'꯿Xwmt a2NxGk\eܫv ViSaz)VEyL#Bkh~XY?u>҄ٲ+F`heͼQ8q /8ΗI{,n*Z<ȮaD!Dp0#R}s#bYoz. ! vc,-|D8zENz@'2bY+tձO";"rSHqofHŊfh{r EHɬkH).@j!Xg Hઢ"btķ{SRit6LG^B{m4 zk{>S?wfn)$D&ԩw166輊!b @YakEf[a>wN!mM^M~hxgF7hb ϘHq$%<bBsɹY1ބ@[4E V[ꗤ5iE/$Sll8^]M_?b6_8\ 1XdF"il/3TrwyGv 6G)2|$`|-LwVQcze_dyAx~Ezkj%oT)d0Dp6y][5vra gf\rTnOMO_g͍iQ@#phԌZmB,l0Ɩ298Dls$;.&`D&xC~EP/a G5#dLj5XT.c ꔌQuȫH:%duA Nd%cK4}gtf$? M_) dљeyT$'Q|5:{ɢH m3OANNRsZjn!#u QTʔîm{E6ͦJ|8K$)U8]/fݚЪ&k "ɭE|&cBF#ndvmu6MûͥtUz'tͧ6J;ݢy0UkvULx? >בQĒh@?44 iљV.sҕۇUٻ7},NhOgkmw-o<],h;d8e-d)eLzjF_6حxP90֏l?CQJ%:{{|-dAb-GEN"*T؋گIФfO/PNH2 e47B?i'H BmZ.Whԅ8QL}!nlnJQQF1j&ݠ`n4D[4R*flBՈQgh?cYa(߁cBfURS VrQ\T+=HEa3Z%gv4LJX^!㓧536gyZwM{dd0zr(v_4!"„ן֌e }=dD=/ RqQMLqml qmlproject source.qmlxڭVݒ4vma.t`.pG fd[I*R*ts\b9ޟLNb|HzR!et.~7hΠ_g3Z.{6B@.#O[ym0/kQeIβR3ސ6p!Ѹy6t< 2.|kM7M~ 3P$g@h:aa~;cn48ڨꈊ *0C:'T`3л]* V3z_*5bG 0+p:??A͢UdOeJPBwe>8NxE!7ti-/d4Dz4V5^\)C7YpvBE-@߮;%2l8|0S:>wNge=h_R7fy(697hO]& G, HMjRql1,hNXFѕGi;mڋp߳ PX5N[;[m {-tdI%,Quі-j`5!9g^_U67ݯ̛kv}9bP_j@f >9~5P)̛y,i^Q5ԅץDr I$- %UET WWcVaXdTB"uKuS *] Z*s9J qi#̙Dڑ %/%/ bKIQ)JNeQamWq8;+5&H*u3NB ]!E#osH8cjanvc/AT1cE͢qPلz#[;Iy2Wd> O6I%HQԼ{kO%q.I,>XXpRacketrkt source.racketbxڵUn0NZH !~VkPg븕T0o+{MW@?w=66y OnYsb7v~=og>hfKCcvcGQh,bEHrBq1TѤdG8Z~[C xV|zL tE/< .ڞs؝P|P[W⺑GO>j~f4#eGwpY| ]%8S9yذ /@9 g0%:O >tn% ,` rw _ƛg]EjI(,}T_4-+vhzޣEB↺L,.K{T4itE`aa?T=PW;pnz"@l?|zRegorego source.rego xڝV_o6.Mm؀ ZR )Ɍ>lOC?@Mנ%&B*E5ǞdQiu}w<ss颳=[I|VkuKzP8Ɣ^IVTaxJbl E^mZ~`ﶰLzInćYyO4ҞeA$Db%*ck2 +\EdldO-xJs+-ߛ;iCE$NCsos{4^%3깃cW|Ȅ"Q ];wybtJ4_4JRNey4Qq`}wp}Xߑ2r`+XD8bQHf)4ٚfS×L\Q'8qQs'cC8 -&Bc1kO/$=?|~=|!=xڽWn0n76 \~ u%}ށuWFMq: wMp8MV!,'=9>w>ݮ-ֳ6 kɕ!s$e2Rf-_Dʬ˜J'2gRf#!ߧOGߧP|VHS+!ABO nQ-g0|[ ;pO㷼ۿϲTQ>|(\]Ht_=5^O "C&skq /@PSFkrl%%JG!5( F80 \kd+rVHKAYJҲ_=-yFWiɱHK3  XŬ0)xRb]H6J[oJNk-MmɺhVcΈqZл=>?Ɏ"BJ5-;h3hH"CzSāg+C6X>3q,e!fm5NfAuKf MAlZ#:Eק&؜kuꚦK[XRS Q{G[~d'EWݒK&Ⱝ#؞y7D|luqpuROZЖAOՕ1箧φܝ+B FjೇD;M%A+}Ak-~|w16*Wey[9IيxOUr^1iu*EܓZ֨4WOsHސU*>&resolv resolv.conf source.resolv$xڅN0 [W\V4HSX[$.;T)%It dg;7W6q{~eQ$syYJ2 }=j6>' -ۗ Gῦs1O|=i ІޯP8e>偁@AFFgY"vb14!si5!tdn`ob0(z^ߥ([лIL+2n٬asL 2SOYQ(4FCk9wЎ;Robot Frameworkrobotresource source.robotxڥTmO0nې6ioڄK" !PB˦ImHqƔh]_ CZuF=uǹ]!ӘDt6ĴLL}v̦7;f,̎yobs ~nR* HT!,H ]FbvDZ)^Pɯb:M6,<&ܶmenwGP#۫3^^r]Δ}W gkhs*Cuybѥ7kV^ՎgҳG]Jxd zF+p]p>Idlo0p_ed0L,9ŅE#d#_܁`-t}|Sм..͕prٟF%t"NDmcD-RIO RY]"[d[^~1MW*L'\+ey6x7I3xX:syNV:;+1-LMPpe.D~)_ 1I}DU2ǔ_2pA(D=ѻ#w d(.Hj8GL-ח#Z6P"Tżm]tIp28_"!QD0GIh~- Hm[4"T/ƯOX/%nVrhcwc /ND4ʒ9L<BI%Mi ,տ^rPSCSSscss source.scssescape(?:{{unicode}}|\\[^\n\f\h])ident*(?:--{{nmchar}}+|-?{{nmstart}}{{nmchar}}*)nmchar"(?:[[-\w]{{nonascii}}]|{{escape}})nmstart&(?:[[_a-zA-Z]{{nonascii}}]|{{escape}})nonascii$[\p{L}\p{M}\p{S}\p{N}&&[^[:ascii:]]]unicode\\\h{1,6}[ \t\n\f]?Fx][Fvc[We˖=nSw=1NIvZ+ɚnZldk_H~PlX yN} ȩ:u"="i.NW^;Q^O:,g~P Y'>m:쨨zxW_ˊ:*~#+^xd7:*~'+nwT^V|}jKowTT+xJGEtTT+nGE訨VjGE:*|ZWPqz{=Ŝ)|^OF{=ř)|^O3WͦӢp$7'bbI؆ΪJw:*IYUmo I(팔sMΪֻciYLIY&uT}睊ˮjJJtTG]5T Z5(𨌦WaqION4~O:*k\ ~rũ~q(P 9Lߎb6QK gYلuDvU_U{4\K={aѓ_?=7?^Ti. }u3> ~n'AۧQ/_3A %CU]hwvVDᖏW^+l/vJnB?]BcmǮ|bİ@fW>w*a`_e-wy]`2*yIaMB׻YfшҎk}EOGt&Ĉ>i Yvi-Gi= }ͱ}@˳X|6d-Kf*=^snv'SXbOtTѸ=gsV_Ķp ߶Y9|Їt7h>YPt"Wv}^/o:f˹n)l)'&nIJU* t$@nip |i6VV\6˛[욡GxV$Nw$!ÅK.ћO;nKY3.\F^^JS^n`e1O]q@ ->SvZPo p#cF..Β~XE2c/!4&4ͳ4'ъ `YVӌbʺ؟>Mφ(\MY1vE*)1))JAZ?.)u+ a"(;8ΛVrϰCtqh.@9z.D7 Nc3j.^Q@$?ku$SRcal(7>luM"t(q7$L5w ~qJ ]EKx{S3{Fz͈0k kȤXՆvVqۚdn PO>\#I"V0#_료_2b1be: ȝg\N 3pm[W|;!A )]>D#(mcp7a`?4rk.amTwm /S^ʺ#YRyӿ(ulwGd,Cae}Dǁ*%_iLqMҤӨ*g&Hr A*C qK'xt4àY?*57 Zc".f[+ wA?jQZ+aqiYn6`y!iE$ڴskF=UD)tMqB,Rϯ8ˣ/+P~}ţ'=˯mnّ׼@[{'l<I]=͑S Fv tYZ[ki4)6FRvc*'k|쳶[Kzl_S^9Aec@QM'I/mp}cPyXhfυT!%O{mC"X_ (*sJʲ( o5-:X3 q2`:ꏺ5EedFJQw;-F'oƀZd :KNް8#)f$Oc:$#|d<0sNS),R?7q6KΛË0`7De +KmXDLs!Sӹݶ6eHIY"U^\5q?'`v3Y fMM2-\tͪǦC-ُQY uxp4r|?v^g۞jQhj,u{z:k2 "7>kXkS'I,!J4@kHusAլxM2%*iۏ9kC6:{o8AnM슅W+ چOpgXK?^쑭(Lo cGÚlh`<9~4xʛ>vN+2K0 mcF&#jot޳f1FZR t =\,M{z}؂ed<ӘY..MSm_yy#m[֖K1<{C/]ixy껆P eC7 -p<Ʌ62JHmeĒ!J&âHBQI)OOKBcJHYXԐ'x( TF A!2JYja 8xb8#Q/l()1xb\1Sؐ "^"p ao#,`l{#_V 8^$( b @)Qa`6^jd~Akv95|-!GoFȺiO9{a' `pkM/3U@JMK XFf&ΌkSI(feT$3l/%,Bخ* 4 تxfuh?Rjx 3˒tTmDqx t&oN !'~^<gNtqp&) }1$+Cs)K]S+4 G'`Y`cqĊ[%`3) ˑ% 18x]@bÀ4iY2nР !GC0HUN3H*EL5fG㔂isԢ8'"JLe`Y)$2B7: JIvHK2>COF}Ğz&.|`I-<|]ӨtݹHAPA6 !r+u;hyèJ+5Dt\54nÖDΦ']Dr%A:LXXhf-UQ1(eHh&:b?AAgX08av)'PSYU&o8)[X|p Mad@<ǸB _uqQ@{gf@yhqӃ++KdXS7(0FYr/~eSM^ 4,DXb$x-,q2Nh9D503:x2-\HyPB.N"!9L_,hN τp^2p):eSIqBr98jV.GP|k88*qJ:7$s'L7m&y<)~5S9^]Nq2qT$G8\+#*S_B$t!]l%56*0zT ҳXb T܊Mms{h)wn̄:;zZb 1Nj$⾗L|gJr3 tʔ5V-s扶4%R%Eu[J-eIq ȟ%{|h)>'迄8)n' ot ߤs{1,3}|[~+])counter_styles#(?xi: arabic-indic | armenian | bengali | cambodian | circle | cjk-decimal | cjk-earthly-branch | cjk-heavenly-stem | decimal-leading-zero | decimal | devanagari | disclosure-closed | disclosure-open | disc | ethiopic-numeric | georgian | gujarati | gurmukhi | hebrew | hiragana-iroha | hiragana | japanese-formal | japanese-informal | kannada | katakana-iroha | katakana | khmer | korean-hangul-formal | korean-hanja-formal | korean-hanja-informal | lao | lower-alpha | lower-armenian | lower-greek | lower-latin | lower-roman | malayalam | mongolian | myanmar | oriya | persian | simp-chinese-formal | simp-chinese-informal | square | tamil | telugu | thai | tibetan | trad-chinese-formal | trad-chinese-informal | upper-alpha | upper-armenian | upper-latin | upper-roman )custom_element_chars;(?x: [-_a-z0-9\x{00B7}] | [\x{00C0}-\x{00D6}] | [\x{00D8}-\x{00F6}] | [\x{00F8}-\x{02FF}] | [\x{0300}-\x{037D}] | [\x{037F}-\x{1FFF}] | [\x{200C}-\x{200D}] | [\x{203F}-\x{2040}] | [\x{2070}-\x{218F}] | [\x{2C00}-\x{2FEF}] | [\x{3001}-\x{D7FF}] | [\x{F900}-\x{FDCF}] | [\x{FDF0}-\x{FFFD}] | [\x{10000}-\x{EFFFF}] )custom_element_tagsC\b[a-z]{{custom_element_chars}}*-{{custom_element_chars}}*{{break}}custom_elementsS\b([a-z](?:{{custom_element_chars}})*-(?:{{custom_element_chars}})*)\b(?!{{ident}})duration_units (?i:s|ms) element_names\b(a|abbr|acronym|address|applet|area|article|aside|audio|b|base|basefont|bdi|bdo|big|blockquote|body|br|button|canvas|caption|cite|code|col|colgroup|content|data|datalist|dd|del|details|dfn|dir|dialog|div|dl|dt|element|em|embed|eventsource|fieldset|figure|figcaption|footer|form|frame|frameset|h[1-6]|head|header|hgroup|hr|html|i|iframe|img|input|ins|isindex|kbd|keygen|label|legend|li|link|main|map|mark|menu|meta|meter|nav|noframes|noscript|object|ol|optgroup|option|output|p|param|picture|pre|progress|q|rp|rt|rtc|s|samp|script|section|select|shadow|small|source|span|strike|strong|style|sub|summary|sup|svg|table|tbody|td|template|textarea|tfoot|th|thead|time|title|tr|track|tt|u|ul|var|video|wbr|xmp|circle|clipPath|defs|ellipse|filter|foreignObject|g|glyph|glyphRef|image|line|linearGradient|marker|mask|path|pattern|polygon|polyline|radialGradient|rect|stop|switch|symbol|text|textPath|tref|tspan|use)\bescape(?:{{unicode}}|\\[^\n\f\h])float4([-+]?)(\d*(\.)\d+(?:[eE][-+]?\d+)?|\d+[eE][-+]?\d+)font_relative_lengths(?i:em|ex|ch|rem)frequency_units (?i:Hz|kHz)functional_pseudo_classes`\b(dir|lang|matches|not|has|drop|nth-last-child|nth-child|nth-last-of-type|nth-of-type){{break}}ident*(?:--{{nmchar}}+|-?{{nmstart}}{{nmchar}}*)integer ([-+]?)(\d+)nmchar"(?:[[-\w]{{nonascii}}]|{{escape}})nmstart&(?:[[_a-zA-Z]{{nonascii}}]|{{escape}})nonascii$[\p{L}\p{M}\p{S}\p{N}&&[[:^ascii:]]]property_names)\b(?x)( display|width|background-color|height|position|font-family|font-weight | top|opacity|cursor|background-image|right|visibility|box-sizing | user-select|left|float|margin-left|margin-top|line-height | padding-left|z-index|margin-bottom|margin-right|margin | vertical-align|padding-top|white-space|border-radius|padding-bottom | padding-right|padding|bottom|clear|max-width|box-shadow|content | border-color|min-height|min-width|font-style|border-width | border-collapse|background-size|text-overflow|max-height|text-transform | text-shadow|text-indent|border-style|overflow-y|list-style-type | word-wrap|border-spacing|appearance|zoom|overflow-x|border-top-left-radius | border-bottom-left-radius|border-top-color|pointer-events | border-bottom-color|align-items|justify-content|letter-spacing | border-top-right-radius|border-bottom-right-radius|border-right-width | font-smoothing|border-bottom-width|border-right-color|direction | border-top-width|src|border-left-color|border-left-width | tap-highlight-color|table-layout|background-clip|word-break | transform-origin|resize|filter|backdrop-filter|backface-visibility|text-rendering | box-orient|transition-property|transition-duration|word-spacing | quotes|outline-offset|animation-timing-function|animation-duration | animation-name|transition-timing-function|border-bottom-style | border-bottom|transition-delay|transition|unicode-bidi|border-top-style | border-top|unicode-range|list-style-position|orphans|outline-width | line-clamp|order|flex-direction|box-pack|animation-fill-mode | outline-color|list-style-image|list-style|touch-action|flex-grow | border-left-style|border-left|animation-iteration-count | page-break-inside|box-flex|box-align|page-break-after|animation-delay | widows|border-right-style|border-right|flex-align|outline-style | outline|background-origin|animation-direction|fill-opacity | background-attachment|flex-wrap|transform-style|counter-increment | overflow-wrap|counter-reset|animation-play-state|animation | will-change|box-ordinal-group|image-rendering|mask-image|flex-flow | background-position-y|stroke-width|background-position-x|background-position | background-blend-mode|flex-shrink|flex-basis|flex-order|flex-item-align | flex-line-pack|flex-negative|flex-pack|flex-positive|flex-preferred-size | flex|user-drag|font-stretch|column-count|empty-cells|align-self | caption-side|mask-size|column-gap|mask-repeat|box-direction | font-feature-settings|mask-position|align-content|object-fit | columns|text-fill-color|clip-path|stop-color|font-kerning | page-break-before|stroke-dasharray|size|fill-rule|border-image-slice | column-width|break-inside|column-break-before|border-image-width | stroke-dashoffset|border-image-repeat|border-image-outset|line-break | stroke-linejoin|stroke-linecap|stroke-miterlimit|stroke-opacity | stroke|shape-rendering|border-image-source|border-image|border | tab-size|writing-mode|perspective-origin-y|perspective-origin-x | perspective-origin|perspective|text-align-last|text-align|clip-rule | clip|text-anchor|column-rule-color|box-decoration-break|column-fill | fill|column-rule-style|mix-blend-mode|text-emphasis-color | baseline-shift|dominant-baseline|page|alignment-baseline | column-rule-width|column-rule|break-after|font-variant-ligatures | transform-origin-y|transform-origin-x|transform|object-position | break-before|column-span|isolation|shape-outside|all | color-interpolation-filters|marker|marker-end|marker-start | marker-mid|color-rendering|color-interpolation|background-repeat-x | background-repeat-y|background-repeat|background|mask-type | flood-color|flood-opacity|text-orientation|mask-composite | text-emphasis-style|paint-order|lighting-color|shape-margin | text-emphasis-position|text-emphasis|shape-image-threshold | mask-clip|mask-origin|mask|font-variant-caps|font-variant-alternates | font-variant-east-asian|font-variant-numeric|font-variant-position | font-variant|font-size-adjust|font-size|font-language-override | font-display|font-synthesis|font|line-box-contain|text-justify | text-decoration-color|text-decoration-style|text-decoration-line | text-decoration|text-underline-position|grid-template-rows | grid-template-columns|grid-template-areas|grid-template|rotate|scale | translate|scroll-behavior|grid-column-start|grid-column-end | grid-column-gap|grid-row-start|grid-row-end|grid-auto-rows | grid-area|grid-auto-flow|grid-auto-columns|image-orientation | hyphens|overflow-scrolling|overflow|color-profile|kerning | nbsp-mode|color|image-resolution|grid-row-gap|grid-row|grid-column | blend-mode|azimuth|pause-after|pause-before|pause|pitch-range|pitch | text-height|system|negative|prefix|suffix|range|pad|fallback | additive-symbols|symbols|speak-as|speak|grid-gap|grid ){{break}}pseudo_elements(?x: (:{1,2})(?:before|after|first-line|first-letter) # CSS1 & CSS2 require : or :: | (::)(-(?:moz|ms|webkit)-)?(?:{{ident}}) # CSS3 requires :: ){{break}}regular_pseudo_classes\b(active|any-link|blank|checked|current|default|defined|disabled|drop|empty|enabled|first|first-child|first-of-type|fullscreen|future|focus|focus-visible|focus-within|host|hover|indeterminate|in-range|invalid|last-child|last-of-type|left|link|local-link|only-child|only-of-type|optional|out-of-range|past|placeholder-shown|read-only|read-write|required|right|root|scope|target|target-within|user-invalid|valid|visited)\b(?![-])resolution_units(?i:dpi|dpcm|dppx)unicode\\\h{1,6}[ \t\n\f]?viewport_percentage_lengths(?i:vw|vh|vmin|vmax)Kx}ƕ(%[V\bݎcGŅ Y$$߿r ("{^z)1$˕2ik 7o^7keqԍ97sqϝ{P5SQxٌɌxJf<0#232Jf<8#eƗx匌ʌWxtifz)tFFkgdTM_7#fdT~FFfdTqFFfdTy:`ԠoOԐoOԀ_>=)}E^/FqMH#RwfT7Yo*]2+~e &g0 "_TwNMIѪ%TwW-}O ZB!}UK(4oV-UBcUKEpj $ N&r~j $T-zuj $V-䡪%ZB!UK($yU I^]BG*"G+6qJ!cUK($YZB!kPxm IZB!UK($YZB!몖PH%"zb+$yj $OW-t IXPH>'WxߊRNbRoX@a[K٣vKY <qޙqIKʩ^BgS())lzz}Pr ?^9߷+AJv+eWHZ)…g+eW(\ R)[)sRvn ^9ɈC?ʜNwrayޞ̛i\/!_E f(0=S̪j=SD`iC}n ,N&SʩU0WN-^9.R⡫m^t*eWpV|fdX%jfYW˯fsZ~Ef6WtfZ~EhJH/l?ɧ̬j֟&'wܤ?FtʪPm/-O\/@L.wV-UK(UK( UK(_UK(,D0te$TɭCK{s~VRU-p},ҍ|OQ.{[y[!]RÔSJRuJ):|({C}R 4K*<}B)!^:g~Kwa JUG>t:FI<,ЧSfTu~꬜ 6+ʩr*r-fTgʩϙS["疳cHH߫>!G?biCVR$+VaR|Q 1Jn_R%B/[!ɗUɭ˫VUr+R$~]FVP_[=Rc:}d;Ҫ&06 `7<7Vɭ&)r l_PF!ҷ32ziU>+;fT$;gTf HU32gFFEkwFFEfoFFEaFFE\~`FFEW~pFFER~H|*qГWGyG 8\Dc=Zwˬ?>;'fU[=?%_M27Ǎd[#_x@Aw&]$dΟ/U95 X6P—~A|Kަ•qz?Q/&gT_<:pfC d_+N~}\M)GQfM-4š[ (d._1(ɼS@|/:S0w=pA21`~_|K'^u\P7MI$aaMzd?߯(!XBO 22ןTVȜVBS?/ ܣnҗ(`3}^ۻ˵UkV/$]zGl`t=52ado飪o&[N~ꑽiM !}a 2?jNVXd[Thz-?2e)܅UB̕ 2iV{a̦&sG2KPQ? a ou)eeaNVOdKp 6E6;zʻzASS55))jJ='tʧ0,<2N]6Ԣ`\˪ W3qr$e<Ն{uY8Mf~nG۟_Z%I"-s ^W T9R`6zA21(@iU8:;8Ck]E}探%ʁZQS.P+t>cD? //<_LACˏ7KwZI N3Zθ' .ӶɎ>m>+u8_o4D1}frKe+,7Yү*̭:(fY"h;=6S)[^#llL 豟щ8qYCQlĠ.^ԅU׿[߫EnI%ǁ?++N{cu=z[3weeY7*|doۃWv.v{Y|lV[Yjssw1ǪwZ3n2sdlZV nI-ױ2%8 h')kmٶP,R|dpۅvN7W8t{Y=lk-0"= I /)[fߏH@r_f0R8'2YD0D#ɦMŀx @zնû,żNhdNa~ˌnVR:g e}?ؘ5H`78ivOWh!pԓj4XKhIEQ=X^\8;ѵm:3ρ]s8PsUT +L^:$5aPN@{{S]9o&Xԙ¬`-*wu%g&cT׀t50ms*Q2°v7ih|]KH9{{mZW59Zmeyd:Tv|iPe}Vyu-,Eȭoo/wI*,6 bBo};~n- ξB hxtV>h> "]0P4 `1&`>G.R},]QS/. kq*ӧl'B-^b.<{kU!VEbz?u&*`In-7 b#`uٖ]7K+7 0 :\/Nڶbn)$+s\_Z-uRNJ>˒ϢqjkQO*틥Qٸb:\1k)/!O\b\:%+J󕍝]۳aIqP{v.),L 20vƙD+jjLYlh^u>`2u,-Help΂26&%g_6N MڛiGFP7A{uH݄!^Y uɿZ%Wg,F'DK]b>l.&KlN?=Ԝ\7mK?֤E{ߏՇK.}- ˽ l{6`h6{Go|6z%$֟o|/v=\\_0!._j#{Ua,Ei2P4+O ;CR}Qz\^k~n  }FjeY9Iz^?kc'e> xXDfǽOJ䎳&\L%fp;X^Lvq/y5+LO]?_bP~C}(Wox>g%ANe~/coOX7S?z@W ,T,~v$چLlY]' m7QFu >gC7o>B.r:u"q~i!MqBp>'1՗n[cgT#B GxΏ!Iw?>C.s k|ߠ # xj xN/NxGUJ4c͍"s)sd><n *+ϓϸT,.0 GԞxˡﯩ7C7bw !ԆqԏC^pˍnOqla3@JsTcҳc7!sfpEdAس } X)#F]abt^<n:yоR&`/E"bפbU|"A/yQŦ<2M#G@w)T-be[T ;ۨM殸3lZ F2eCv[=꒳>g˷9`qը^jE _jʳ̲.Zb?q_ C=}!*+T^Px<^b16ۼ9?lX߾Q-Uyav/5ZZ*/\Sf@~ْ+tCn ݾ*e hF;n[sJb5Zz_8:U]{Q{Sر>RGӥ1 úfKy+iPÁTk(NȊñtPЯ|j7蓟YK@?!!^EbCZ`~v*jmz$9b|"A7N"_@ *ɈpbQT\ZQ\Hu-۵5}$d6%eVN:o57'Nv'҇oqh l2VrwOu],YᶊZjYqދYy~;]]ոx$!Qߵe3ecL}<8K'vEE ~7]6iT6aB&E)D͍MG_]F(Wzyfk (@QOpS@ O]7bqJ Z(]{y|o/qd 8qFoK[|k!@¦7XFLg0>j@Co_x/[D7ZPxX y37 6"U|r=|=zǴ/ʅY⼼*[.S賍KalFC+pC ?1 3O#n8gbڶk_k"s~9_|y_̅*t MnZv;nacjt9y2BeA} `Rv Vܼ_~9/hfy7ffs֊􋠫9 B.Fйޫ@ҽI#g`A 9ADZX*31-`{ A<۟oρ/hОA6B;ׁ9}ڶ\q. E@JC.49c5;T7U71L7+7NRsRh8߇n˺k{'s-"^I~uQV&{ƪ6b/B9# zJ>/6w+\sy911aR]fi^*Xց$h%6f3xYzj]j[F-nc܎n-'PXvcS)H|K`WŘ> ߐrߐwQJ* Ħ!(I !ja0·ϢW5KzkceaLLؕ9Ӳr{LnH]@#XYb/ٷmgG|Zݗފf\f@:Lw7{6>^d#ġ7Pۉ̝+EnBXawVŴ;bҎx) 0u/)uPN $Rs;g^]~Yg5>=J>~7ځ%ﳪkm}}*Jr$Rj}GVwlS7*ƭjczW޵0}W)" \bTX/TY/-|s]5 Uڑ @ Cl~D.,]~>w ]s ]SoLsѷq8[?RRHqҝTS?eS2;'OhOzYOJlRq#~<008A?NVZxB͌3c"tbV6Cl ;.NV=p =s'y$yӚ1F~Ok5};v{aXS&67N'i 28a /׫Ů=懩H|kgZţp;o n1?IDaK̡Xh|k>{~gܯ0Ͳ?Z7.ftZؕ8δN=Ӣo/ՉfS[GgtݘCaDLR:Y4N9j,_1ctU+,)rBV&XD =4lpcWhSxXr}KT9d͸֗HS}D ܡgmOm\؛b^>D|X@xh 6ϱ!&7I)Àa(=1)%j147_d5nI'Т\907)푭e~ҽ<+xX,/>*w,)}ݍ&I2PҖ4G֌ZFKNuY̗lpw@m;bX#B׏,oqwz1ԷC a@!t*w]z-[ܝxY)VtffQ^orܱyņF,ޅzY;Y"s4uW;\/X6<4r>y;* I3-5fnfL ;2;˦s☤e*on"4хݡM7= fS[" G772Dukd&[0-gkv;+|詀2YȈ\Jyi=8=~Βn b߳%O-fI]zsDj3>1%0@CF  {*4 >oI>^akJdږcuD[<{_ҭa'[6p MeLmuycauu}y[0$`S{Fsl56MM N:pG hβM3_oGqZzʶfq?9qrOm鴭ۮcňSySp:xj0Ѭ Y# vۦ'G}憣ܪȨvms 3F;iT+%^,DH)z܀OJntVףjH ,Jϟ_cͶ;\F_k+//u9~l*.p}VE9hBRMX3w؊Ѱڰ~nͯfnΛ#t[kk͵*:vQ[h=jy_}^-IyJ ufegS=_a뙻[]7瓌M=;v99[Yo.[b nf[,1$c |zgq2 pOX!+wF0S]=7J*;&aa^ cG# "x<7N|S'H! NKcPb uxq=օm)$K|t.F'F'<'Œn S1*Z7 @'6pz%"\z&cwjxHdU}7&:q<3G Ztm yc/ 4~' 1a`8@0ӌw҉q 8U,/ѐQ 8<2N3S?fzl1>m#Ç)$D'v'5)s "IO6Y/ 8DV?3 }Y_%]x3#wn48nw0z3((*"9I*t$r9 RB @D>5\@j(rz>9sWlsbm,X19AOTU]٤ >)I,H4rm\pua𒁘}e6܁5aT@XE3j7J'+OKM~!94ܼ߫&Sva)`.SCa߷v_#7kjrG+oHRq?պpt*GZ+BOِiC6$C'trOkVt9<|7H=>:)j8SZ#)Zއ9JSE'Gx=VÓN?BԧZ;{'ԧNӧ>.AcSyoH{AFQ>=3 8{-H~)ynOk낞/_|)Iōnv O@9s!}qP@xHlO.('9xw-0g^@a_g=HFmdwT#U* rJ+@ZG(yM5-exďXxL5&Ǵ@N: iw= >{܉\vu^0q_8֮8JC|}|/|_W:"P=M9eD|͟  2C ]$JC(}(-҂(4"bͤ7ڨLFdrA&d"'+ti7٧8ojJrb(śRÒf'*tO.HǡڮVﲤ%=sqSgjM,#. < ?(d:IJt|'&rbŸg`89v/ktb+slnhN3eƾÏ̱ʔEFTzW_Рcfuj`NW*c}9sz[v)u PZ-3V_ nA?/÷ϒqyt j*v~ny]{iMNI9Gт|`@8iҝ I:34Դv]dw?* ԮUl'zTÍ>85ΘLJM뇍ΘW =}Y=|]W/\fbwݒ^7-?+?Vdv.EeW5.`w_h4vʱܟooݫ0ֲN| u7{]Z๣H~?qvu5iw|5(Ú`؟\|1JWž{<1ni. i|!ʐ,,Kt^_!}~K_kT`guXS aa[u !Y lF,4Pnu/(`]$`VKL-9[XFUI|_EI8(mȼ1}A٭Y<{,*[5cR?Pkr7,WZYR_[ʒۖ>>V^%ju >Rz*oX?V(c#!HB1"D'q u4fFÖg8aBKGNa^y⼺ 毁6;*3 lZl \V,z|x`:]qNdA}y&|:*wMZ;< IU"n%S:h)sWl+}i-ᚌum}q]YTHȐ:Ԃ b X!DX6I6S~ra3zޠ @$@ Bq> i 3vM' GuZ\HÂu(ECuh|@nЃ0|5bJ!mV%,M ʣ|i4O~@:;3PqFĽ^F@$HVJEI7]-I3#8 ; t\ iڲS:NFSC^ych("4H!Ur4>O;p\^/5ش0ҠdDa]r8ts }!4|3OLªH[atΫ"R"֢]JMȋYvHT3_dc m( E<@{1%ƤbWa_oSH;;ӨU F&MĆcw5ߙ`1TͲb%7sM?j($C[L#vk;YS-OPwPU7zۗri f?rK*v`ozTG%CLsJS#},&̘>`)/oRngq%~XixK)'„|I#$M5?$d\` (vkJfaT?e-~11*ɩֶFF5Z,\G(oj"L= `ut)M )Ik?*4"G4OVC]! MA«s @qD̠ NCJtla()0W75fW/h"/FIN#rUD~A3}G:`DueӏǦO9-w1ۑ YP@za&鴅9RDԘ-՘tX%R.6?qGS#@S 1Cl~GjWA%_Jܢ=ﶢlO1!:>ƟN -=A|17ޘ ΅s\(8 ΅s\(8 ΅s\(8 ΅s\(8 ΅sɻ O74x㓎L֌[<&ԔZV}kt6R`╔|ºJʊ0-ݛ[nqy9oY=6o/X֠~ D(A g]VW[ V80U8^m?]۔#Uoj}]Q/Ȋ"2;V&$D"v@%1:}r6P,W*P4,QVTwDb[ (^$C].$r6LYJ >]%O@)qCGbsJs4I[h^gIPpg&LJ'*&:9GŸ&iB&[FsR !~<º*]IJ/S|E&pH3TMKL4혦S C'ȐE\ ݉Z9@)Z T`*VB27M蓥H'0\ f<(MFzubA 4 Fa T\e\*HLݣIy夈tXnaZ_ZRLR*EҐ qSGV.ߛPq)m0c 2Cᘡ$3cD]Z\@d@&TtH'*3&ǧ LBhB)՝qC Ii,WIЕ%&\-7mAe"zLw5e=Z|--L J5bH˨QwDL: n6 jFio14LW3+4s3,JƵL#q˘e<4OȔrڐiؖL0md/gat⻥Dڝ "=~A|bñ&ƕypDjo2>-3GUdAIؿxRfBA/Z(fc@P!)jzd*\g؊ۘr?t<s|jC6skKBGZ`?hv̫J˽ $l~pDQI] 'r;Җ PM!YzxMAC G:\P7~epbH59yu`!Y9jt4'+Y8)~"l77 ::-9%Vph>M-e&UCJhYj$7drUB *0Ҕ7 ,k&F@Z Mݓ_YT4 2a[~ °)'+bY ȞA(FGuFp)g$qoIÒ#ws3NɼK,l Ɩ8 @,UU .Ixfq $2\(FpS+B{E]jkl\-A*|H $&{nd 4|yHn):==|\l2$/`li#tX.-e, 7`}hL:(g h55Eb&~'f K\0`0B8 =j =1AE3GwDЙ3-&npi&MeRk9n ] bTYYA/I{ߐRGj 1 a~u)X3XD&2$[()-Dcw]$*4fOP:7\X,I&w͆o>}eA!.!` 0ľP4i 6_3v/Mrv% I p7 q3c`Ίu')Ds)sƉY`w0CjoTrbgLyfK-J5<*G!PZLIL,GmP´tj%ԣ=<,R".'PSI,ϑGfz)AMru'q?A4q-2q/;|=Q7oM*tB,-$T `Q|gIpC%V̸t_!byNµ*F_Z> H{\}uޒ]HyQOMwd9HiOL4ZMYRG!"bLd=>P.5s 2Q8w$.(aGwCBA͢bUhDVaJ'ꉤi LΝ#IRmr T#Fctm! 0XFdHY PL̆)<1;]DiH kS!ILzI LsjB\C~u̠DȚ9RqIiZ̎.g8lYhZRe$Y$fKU[ÛpӍ; ,Cڇ}no[`Q w1 )#՘D>"`#cAn@v+Q9RʍTrj6[>]nM͒1TZTZiQI÷Lko\SRÖC/pvp[KMg" r^TFqFfԏ5mԈMWUDbN%LZ6Nk.ԣøBFqCiѨsvm๳bޒ7fVLUKZkIK-[/hIJa_"=]4b)¾Q O/n1Ȭcr'Y5V9V@nN̟o^6(POo@|y-5/2pCaa%Fw4.}1-ךEwAoNW̿€0_\K%xq&CQ|}I!1ЂsJuݣ_ib1.-Am/WE2L2ST1mg\BZ1hŚm\axA'ڐ@"Tőo #fAީr'6`cW5WyQA fQv:&{W5, d?0:$lDEx!:7+t:@>3~dU J$A@@HWqPJ]H?㿂2_%phS oWBr%aP{"Bs8"z~ZT<ɾKGP8*s=]&ø }hT+\Jص\rI% _Q0~~ Be,=?Y.}`\bBtq-ù4wqpţVs;RMJ3mk+/iHU6rab5uǖ_~:1ˍoo;Bi"7Vj-&Z=ޚ(<:}p+on~Fߥ]ML!8ʮ=Xi; ?>;gnYIi_q쵏'/~= Z;?^n/{?qv/|uw0 (_nSKl]=4"6F&e\,a5/UV{މ+U:`sM2.TD^Ow`q>r!t qվt0.d,l P!ԀҤ\X ean Xt nB9ҷ'~γw' 8>Uis\8Aky}lY<_xz_9qr4GY%?tpdtYl<h+Wa<>Z V8d Чq~ݝ C;hh4ArtPLLhk/ʖ!^@m|H./=u(FhM AzgtK:Šz_@ шxXc(g yI enI >|3~cP^@FuyWS!rB?KavzKK&g&^CǪ+)WueRij*N_%? ^^뽢zփt^#w:}GVd[AÌ_jopP Գ?\d/N)( *͏V6:uj?^}.7 DX4 Salt State (SLS)sls source.slsxڅK 0+ x!Uʕ.z7JQT|<t*17d$PXD{U&)L&:.!}ﳈUO[9>Re 3mg˞л q1B. xHtJu}?䝈q\H0. p(4WJaTKQ'*#ę-!5ebdl%=Է_ZΏLpC.CSMLsmlcmsig source.smlxڍSn@K?BJ+ť q1^a:?hރAo׉k*#~ػ2dBNzÕwZᓽ6LN}a@ׅz搫iuYx=_Tp/<i=뜝J^ Fݬ lrscwfs"t%c 4ڎy7{3{W8BG4I1uT)5*G4w 065b[!#Gk}V#t51-DGۚt Pw{Y[Wa_5VEMl/q{*nXMdi(7ճ];kbXnjS̫<~hfJ1nz^q$ɻx9BpvSj,x&'L*KEk s7ի#N}5C'%aZ4((6j%:(I4?$se>MfR3%BOuF; | UQiIf1w z]5<-* s2z>.S\NID0d;z= :NjW^DsBoށ;)?LlkG8 '?0'G?RT!#X#e!`I_Ʌ@!8J|#Ss)6' ]/BJ!j g!&~3yl#{8֮m}r1-\⣶VFgQ䂝/oM#A[K̚c2IqY'Y['irZː;GD&:-KZdU. QԠ+MnYNNTݠ2AiNK)<:7Rq-5^#4'%# ݒqU>QtF+H=9FJH+M)ΨAßXQt+rmGیm X9d0uf"Ͱ2'/ ]yM|Q\-.oStracestrace source.stracexڝN0 [:4. 8pAy.A=41bK M44lzƑӲ\Z~lcNVsfQgi@O]]LN-4IF rapFֻIgNy?7P©Ъ{WejhjAgviR d9 8N_qddL>9-@yvߋ6|̮ްaV[LZ?ZzWC0]1, دk Stylusstylstylus source.stylusx\rƙ(˱;Nb;NPO1%f$eor(3虁H-_V_eab` 4zú*Fǯ{=?nzQ}eYgsSA'j{+8 g,-[_Ϗ?6N&,ʴzWڌ W+uh@U݁ekJ屗]o0`Ū>IYT{vAd^OQר_TxVuѽ\羪bs7U5yKy_RS睯 yWis|eϫux30HԲVʪ :M̏(mil\*f²Y/ >f&E" KC">,ͼ$SnQ5eST7 &]⩛6M)uZT0uc,xS0Nh4jlRQLMBRUnbhU]EJ[^)4`kzh捘 a15:5$NvS:A4\XAgJנ&tJP6ȫz!%5.MX 6gPj†,aѠ?JAWw4T[ 5уT;SBLHwͪA4 h QTUcm @8c^~TtgH^?, ?>̏W 1aYD4tM{JQE)wXTVbԔ&So9~zтxpwpq|Bo:fD?(f/Ḍ7wZ{uc*ЙEjx>f;쥇Nuҧh-ȗ2,x*&lȯ2;˙k%J>e)HtXdfR+a#vj-Ygݻm^{w?%ܻu(8}8N/ݶЯ?/ x ܺa]MWOxdԗ:ӮrLRR]0!ss]QX?̹Gz"#nW_>Q{%ݽr`x>[VEЫMwr?)^G0Ύq5-e7~^8{l=c dG5V)mvWZ[˭4FK ރݝtyzƨT<-\JW3=g[vn`_ǯ|74֍8_'!\Ӡe!\_4&5VS(wV:kK=c ?6WWkǧ+ff&)iLaSVpo)G|d ~W\m5*e78ܾl8c1g@1I:Nc,a6NGk޷_^G*?dz~9۵D& TE Od~noAy(@~@~)q g w^w1&xAƣ`P!į )jujgk -} 7kwvtNee"=6G|vAwWcQI ϨE}ò!aVԾݬ|vSg͖b<63v&i(P~J3n;"щuZk-V{u9в.WEF>/b~ 8|8ěsw1>/<ctLa5 NcAh *7\^p>[frJKSvbe`B>ǃLzÅSO.4bA/Dv"OH|__w) ܳoq˸@}T[Nw_GA#8sɓ-g̳y4:si (#uquS% cE{q >0~G%e~6Kh}3#}x:n?>ElL?~¼#P+yFp nj%sr Hp3K? KEm›B.04{%)>b>ݐy4 1KbUЛxŧ(:^SocԜL D0Me^:Qmb&'@EɈd!,hvC81!F^A⒏$x$ ҹ"Ga63cY*N ɲЖ /pHII;br4 s~eQ9 uɏ f 0C 7EG&βqFcq)E0a1S\SPxQ~!/?xdf}f 'tr69Q,ōXyv{X:&lʼG'E8OD>O@- x!y?QBxC_f8~:ewx<ˈt ZLcAq vm*;W#zq/`{N O%z'q1$ zQ:0䩺% O {@8DASu%"|OPN– 2'Ew (f@Gagb5ҭ.hzYMB򧙛qa)g#G e9R#%s.\1 cԕuG#:\ >hM|z)b'K`T#OO-ʥla+uw1.REi 0]2f)6uEsV\/wd[ t=8r4swFbLP~J`Xflt?cŸ0uaJT!:6{(/TFI4Ϧ,ۺё(2QGo !=`gcuZǜ5LGΕrB"vM ψjfaɭ>oE mm0`e_u#RydF ",60+e,x7VWC( Fؚ0(6 6ږ /HOS[ ~/ˍT/ָ:]uնyփ vhVk%\"kլ"k.R$lS9,t4 %`ޱ%, f`I@phUkcd"OT*Ԧ5A^. Tai,Ԇgwwy懲Z q%{W>݆}*.yq֔鋦_voHAt}aϝ-VS eOu rU`H҃rGrAqS9(ieEddcHLvˆO`-VX,N&(RkG11cML.W>w'乚DBᡋ#do&Cҭ}J#ƒj S9h|:>.2l(Â2X 8*-F鐗xi>$-)Nʶ8҆$Gx{G*C?}PuE?3/> Aٰ8ңc+畗~6@O Q$9T>5Ï<(*ߖ'<:mp?AaN%;b#bQ f6>}?f)v[߇3؟&p! ]J7d:dG?_Crc*99bmNb%0K#AЛDl'3V qmY@HLAM, P)a(K2OOP#4T!d ]yɇ @( 28G(|RzNHhW0(GE Rt&ғ y:@`?ks Wk) 6)r! BBzSC`7X6CWDY1%-ёD Pp!`^tLJԛ T! ƀx RH"$.%#*' X8#L0b:w}gY!9e#b\B_q0R,a9L!Z"S{||m>~G99āl8F;dhm:)?#JB .J]6C_GNs']dG`jī\]Id-P LHI92Œӈ%u`h0i YiaOf9vM<#08\sX^XH,0 NX@FR@M@9!Q}D  6 h.XÌrO L;dⳐ+' dl^Ng15߃Sʬ"Wq}vo 4qJ[+GCޙ ]T8gSEz#p_Ɩ>3D1"VfcjtN$TU'-26r=-oo#J޼jh^6I6?|5Tvs]iDkoOh(Hj٘-rӵ߲I͵nvn[pxǒ)%>1a6Op} % J\Pq*D!PܱlL`kԚwb"\1gɽ.YMxb#,s1Ui}{78OTYMPJ6-V2Bo(dX()5hZ==lpDėǂ?bm ؤ'ŠW1ͷ)`]V@w^V(TO)*>3HE%+i?P(`H0k5s܌o&tIPMNgMjJO|1\V?ܰ͒1_Pyz'MZB'/k%*p]˅ _Y6 Lֽg{K*5RAᴟ: gWO."Ʀh8-P v2vERnh :nªׯ?q9{M&AO" zeqᕤmvojަC6}s:/n'?ߍ|\y~4&]k3A7#;^?8!0!8!Ja%{=x4b#AcZi-Mw'IQCgFͧ q)aм'Z5-ΫYG`JG[FSJ=b t 1c@f94&yP? &)tS~Ǥ$mf\%f9Rw (4].#C.W%[ |{gЉA$'9gIMCu]2ga;)Vo5tNA$P͢QqS RH3΢B.//%ׄ}1 蝪<{б]ȅ솉PVB7twl矢 *NWXӪL|]{Uѡ6{퇚C_`wbOUy)Dl @w 6%`&Y'_0sOifR,qZELaUvI]mm #}z';D_oLxma Mӫ߿[iZ[54ƹ13zҥ2-@^likH~wGeY ]_,FhZ [ j(SE:VXVWxh"*%\(94̟hfΑ3QBMڨ7"Β;VZެoV=^ӌK#Ec*}yNJ37vbnoQ/UU^.bIZUʙ>nJ!wk)U#떻Ň%gI+IC^_k(.O^5xc`Soliditysolsource.solidityabstract_keywordabstract attribute+(?:indexed|memory|storage|calldata|payable)comparison_operator!(?:\>(?:\=)?|\<(?:\=)?|\=\=|\!\=)contractcontract contract_likeB(?:(?:{{abstract_keyword}}\s+)?{{contract}})|(?:interface|library) custom_type(?:[A-Za-z_][\w\.]*) ether_units(?:wei|finney|szabo|ether)expression_keyword_binary_logic\b(true|false|null)\b!expression_keyword_error_handling\b(revert|require|assert)\bexpression_keyword_main1\b(new|this|selfdestruct|delegatecall|selector)\bexpression_keyword_otherC\b(now|delete|addmod|mulmod|sha256|keccak256|ripemd160|ecrecover)\b func_modifier>(?:public|private|internal|external|pure|view|payable|virtual)func_modifier_overrideoverride hex_number(?:[+-]?(0[Xx])[0-9a-fA-F_]*) identifier(?:[A-Za-z_]\w*)logical_operator(?:\!|\&\&|\|\|) math_operator$(?:\+|\+\+|\-|\-\-|\*|\*\*|\/|\^|\%)number#(?:[+-]?\.?\d[\d_e]*(?:\.[\de]+)?)?opcodestop|add|mul|sub|div|sdiv|mod|smod|addmod|mulmod|exp|signextend|lt|gt|slt|sgt|eq|iszero|and|or|xor|not|byte|sha3|address|balance|origin|caller|callvalue|calldataload|calldatasize|calldatacopy|codesize|codecopy|gasprice|extcodesize|extcodecopy|blockhash|coinbase|timestamp|number|difficulty|gaslimit|pop|mload|mstore|mstore8|sload|sstore|jump|jumpi|pc|msize|gas|jumpdest|push1|push2|push32|dup1|dup2|dup16|swap1|swap2|swap16|log0|log1|log4|create|call|callcode|return|revert|suicide|staticcall|delegatecall|returndatacopy|returndatasizereserved_keywords\b(alias|apply|auto|copyof|define|immutable|implements|macro|mutable|partial|promise|reference|sealed|sizeof|supports|typedef|unchecked)\bspecial_functionst\b(?:blockhash|gasleft|abi\.decode|abi\.encode|abi\.encodePacked|abi\.encodeWithSelector|abi\.encodeWithSignature)\bspecial_members_keywords\b(length|timestamp)\bspecial_properties\b(?:block\.coinbase|block\.difficulty|block\.gaslimit|block\.number|block\.timestamp|msg\.data|msg\.sender|msg\.sig|msg\.value|msg\.gas|tx\.gasprice|tx\.origin)\b super_keywordsuperternary_operator (?:\:|\?) time_units*(?:seconds|minutes|hours|days|weeks|years)typeQ(?:address\s*payable|address|string|bytes?\d*|int\d*|uint\d*|bool|u?fixed\d+x\d+) type_modifierF(?:private|public|internal|external|constant|immutable|memory|storage)xۖFB@0C rlB2&hGI tٳ/%߲߰Ւnu[6'$U]U]]U]F-VM ]PgK~ LQO9M>Tτv\f:Wg AcFl|"D_ ׂ؆OS[=gܵP뷱3ԾɨCPηi,Ʊ7]t==߃z4 {M|gṕd瘇M~ ?Yud:Z lEnӰ/|.$*MW*4ASe^Pm+Ѳlg -1RyꢁiJZmL˽:`f2)hYlh,iu]5qc_B>= C؃՟cǖ߈-{XS0F޸{: '#'xJ.@.aD4a{p~æ` l Vtu D$rKl z:ĩ~xጶPX 7A@F -it c]Cb`# }X?,x3$E[@=)¿r2;42 Bp{<9w7aHu( &5r'" N8iiPJBH@P/B3F? 1H0V<]Ua.`_Fk)ʿN|v58f 쯚$t&HU<7 "\iyNyew"= 'AR bդnvkKX?eڟh6:l9u+T^%Rڻ(QD`iXۢ]ٸJ{- uEZDټLXmw!}^* xym[Ʈ$HnIʮ!c11 |dQ8ݍ͓70< r:˄ՠ n׈Wp3 .hl8NnG5'9|9cTLG~g5(Y3Y]CQ(]Q>(TQڻ*vENN+y<%!Y|A3g9Y%=EyWQ~UQcatI2Z@;HdwD)/&_*?PR(Q.*_IR{QKiҮ ?Y]Z$/])M>g kEU4J "E;1tvIȝLph(PHq0F3\U t~/t\5«,KVht<)6Y& U *(BHUeѺm=P9 WKhO^ܾ*В|#:D8q%OJiKA2}pP|M1`*O- ]?yZ'xùa2ᚭֆ,[As$F׆{ aM db$mau0"Tyx$*d<ӷ<Кz/3`FNevU=$H좑ny> 0Eh򑐯x A hܹ {7aY$ÁdQNƝE&66!En㱸 >LYNas8Vs8WP7 II>G34=H#줇t !%-FǠwVz ʧ (ɉlԃ=WA\TM{rήْbP$㊴4YiTAʷ3~B;u?^9 ʀl(󄛠6L@}](L|d, b,Es:E#iLI1ɹ*#ySP$&ЖQ/KJd3=zF(=4fhSk+E^TmciE{U᪘oEVB?ѸkuC<hClnF HjvMpd{O%q$=8DN==4FbٓRJp#ݣI 8^6J 't[*b'J~p_=뱹HPFX$B"•VwMw' l=x]vSߘ-83- !U5)nOcs[Q~FvW?v~:֢Aڽ~mrcZ]տxe9eav䜢B}QfdH 4+{TDO˓|!UX(A΄MxeA.>>y/T/`Qad0ގDR(7#Ň`!Ayњm]hي{b60,5 )$۩e[j*^&[Z0f%Ney_9; " ?ED_w}7f·?pL6 >"yԫ*ʯV1гW 8JVYR=8X&/Jy`%J@TFW)8npT.i];:u L\Z*Ģ wمwc3?sM=/YqVypervy source.vyperassignment_operator(?:\=) attribute+(?:indexed|memory|storage|calldata|payable)comparison_operator!(?:\>(?:\=)?|\<(?:\=)?|\=\=|\!\=) contractlike(?:contract|interface|library) custom_type(?:[A-Za-z_][\w\.]*) ether_units(?:wei|finney|szabo|ether)expression_keyword_binary_logic\b(True|False|null)\b!expression_keyword_error_handling\b(revert|require|assert)\bexpression_keyword_main1\b(new|this|selfdestruct|delegatecall|selector)\bexpression_keyword_otherC\b(now|delete|addmod|mulmod|sha256|keccak256|ripemd160|ecrecover)\b func_modifier6(?:public|private|internal|external|pure|view|payable) hex_number(?:[+-]?(0[Xx])[0-9a-fA-F_]*) identifier(?:[A-Za-z_]\w*)language_keyword+\b(if|for|return|or|pass|not|from|import)\blogical_operator(?:\!|\&\&|\|\|) math_operator8(?:\+|\+\+|\-|\-\-|\-\=|\+\=|\*\=|\/\=|\*|\*\*|\/|\^|\%)number(?:[+-]?\d[\d_e]*(?:\.\d+)?)opcodestop|add|mul|sub|div|sdiv|mod|smod|addmod|mulmod|exp|signextend|lt|gt|slt|sgt|eq|iszero|and|or|xor|not|byte|sha3|address|balance|origin|caller|callvalue|calldataload|calldatasize|calldatacopy|codesize|codecopy|gasprice|extcodesize|extcodecopy|blockhash|coinbase|timestamp|number|difficulty|gaslimit|pop|mload|mstore|mstore8|sload|sstore|jump|jumpi|pc|msize|gas|jumpdest|push1|push2|push32|dup1|dup2|dup16|swap1|swap2|swap16|log0|log1|log4|create|call|callcode|return|suicide|staticcallreserved_keywords\b(alias|apply|auto|copyof|define|immutable|implements|macro|mutable|override|partial|promise|reference|sealed|sizeof|supports|typedef|unchecked)\b self_keyword \b(self)\bspecial_functionst\b(?:blockhash|gasleft|abi\.decode|abi\.encode|abi\.encodePacked|abi\.encodeWithSelector|abi\.encodeWithSignature)\bspecial_members_keywords\b(length|timestamp)\bspecial_properties\b(?:block\.coinbase|block\.difficulty|block\.gaslimit|block\.number|block\.timestamp|msg\.data|msg\.sender|msg\.sig|msg\.value|msg\.gas|tx\.gasprice|tx\.origin)\b super_keywordsuperternary_operator (?:\:|\?) time_units*(?:seconds|minutes|hours|days|weeks|years)typeQ(?:address\s*payable|address|string|bytes?\d*|int\d*|uint\d*|bool|u?fixed\d+x\d+) type_modifier<(?:private|public|internal|external|constant|memory|storage)xio64nz^i;Xg'XfKb@? 2haž>_Q%ci)H~Y(>"]?\(?wKp8p"mDD֞nG$Y{&e>gV$OMEN^nz.k]E!=p{kr~D1cW8^Cm < +!kҕ-' !/im ݱlrI+Y{ hBiNɔG1 ڠ>a]ӄ: {8Wu͖TIyf$#(5+0riGXmm{Q ٸ0%h\* m)ipg-(ych9Aƚԣs?tVc >#H!-I R,I@nJVCӁH=ClШ.5t{_Z~p7 8nL-˜:v/O%TI==F"O ͮĈ0:$/ɌpUGQDǭ֚^DP$L``Dc+@>[N;5%}6߆hMmkBS]18yC<8(7uR|{%Eo;e] ekN=L8_ǜ}@hi&pIG!).3c(eKy SA6Ĕ8: Y^r-lk^6M1Uzq/nҥ)7 !͂ }"]7' LǑ.yݠeՠ J>/= {}9D$f4 ~ÙUCEFRAuSxI^8 @[a~Sn<)6iMg{cW~/@+f#4FxZh sUaY Cǝ>.BCWuoP0!)D>..ˆisB2RaRʚe-؊ZՒ5_^(J$'6D}`A;G;.JŰ( 5Ź\ ƘApDAy3*bFޘ8 a'Eeq"KN CC.' (q1 8>|QP+0< $0fHXB]г0ŪHnx8DCl#Y^8;'֦ Wu`C&!A7}^4#MB=l+Χu ^K35Dg"xyq!+/ԳOLbqbh* %@ǝ*+ 櫃Km\s}z%j_t'/-\2u>EɁn.=k*̏iͩX>eq0~0~0~N4E-'[7j'亐үaQ7FϜ')~>Ye+%N.UըGVf_ɉ = Fuv:Ac*+33gY 98w-6nJQjq source.jq identifier%\b{{identifier_start}}[[:alnum:]_]*\bidentifier_start [[:alpha:]_]xoܶn4MI~ަs E`۰/2JѦDlC+HniE=>R/FB9ˣeΥߎ\g$':z@w6r<9JpCp5[j}X=8AR I>opG[؎ }c6KǞsMP侧^ci=\~#'3[vH<m=}tlှkZSO?mg $ˢKOZyտBASJr<=h~pO{ͰDY'u%f0 c77]UN\`^#yC3[Fs?Mtf{h/ '(m[8"I. z5} F0ωֵ>ŶM)O΋ Ltw\ 4ȸw<j?:3Jd& IbʝU8O67|wB+DI:%9zb?m+dr YWba9mc?o#|T{s@)Vݤ! %g669|`3TKdy-ȋVcʒK[k >00t`I[ NoЯ*/K'xǶƯZek7{Z/a0\F窇i RX;HPuI??LA"\`AĺRa;sH[WCx]> ͚7X7nvu=+GעG>Sx7uNlG|-!Ëd]mѯ.z{Ͼt:v10Ɖ'qíMT#}2oP]lshp#:FBq V(OzA(V%:Y=I(Ҹעa0h>T,͙Isj0%L(,JS(v qA$D)~Z M3AyeZc\X`AqIf^,KUsL*;&H #NJ \i {)9T R3B.5 xR@Aѡn_2(ώ `d…T3;Y} H=Xge,hX@#n[?@a92b@1op>o[EFSky&I՜⥚2V Di"+r,Ի#8`$yotK`Vvx(JihުWUGyŘ=M1PDXᰈ5ʢ,:?R:g0MTRNE (Mrf84՚"(@3tz}u[nbEDZ }G(MֺvZ)}bt*-YV(ӍD3sL-H-,,~_*%v3͕A-[?nP$2JUo 0Q ×ӈyCԎM&A ZTO 9IVD]attribute_name_start(?=[^{{attribute_name_char}}])block_tag_name(?ix: address|applet|article|aside|blockquote|center|dd|dir|div|dl|dt|figcaption|figure|footer|frame|frameset|h1|h2|h3|h4|h5|h6|header|iframe|menu|nav|noframes|object|ol|p|pre|section|ul ){{tag_name_break}}custom_element_char(?x: # https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements-core-concepts [-_a-z0-9\x{00B7}] | \\\. | [\x{00C0}-\x{00D6}] | [\x{00D8}-\x{00F6}] | [\x{00F8}-\x{02FF}] | [\x{0300}-\x{037D}] | [\x{037F}-\x{1FFF}] | [\x{200C}-\x{200D}] | [\x{203F}-\x{2040}] | [\x{2070}-\x{218F}] | [\x{2C00}-\x{2FEF}] | [\x{3001}-\x{D7FF}] | [\x{F900}-\x{FDCF}] | [\x{FDF0}-\x{FFFD}] | [\x{10000}-\x{EFFFF}] ) form_tag_name}(?ix: button|datalist|input|label|legend|meter|optgroup|option|output|progress|select|template|textarea ){{tag_name_break}}inline_tag_name^(?ix: abbr|acronym|area|audio|b|base|basefont|bdi|bdo|big|br|canvas|caption|cite|code|del|details|dfn|dialog|em|font|head|html|i|img|ins|isindex|kbd|li|link|map|mark|menu|menuitem|meta|noscript|param|picture|q|rp|rt|rtc|ruby|s|samp|script|small|source|span|strike|strong|style|sub|summary|sup|time|title|track|tt|u|var|video|wbr ){{tag_name_break}}javascript_mime_type(?ix: # https://mimesniff.spec.whatwg.org/#javascript-mime-type (?:application|text)/(?:x-)?(?:java|ecma)script | text/javascript1\.[0-5] | text/jscript | text/livescript )script_close_lookahead(?i:(?=(?:-->\s*)?]unquoted_attribute_break(?=[{{ascii_space}}]|/?>)unquoted_attribute_start(?=[^{{ascii_space}}=>])(x=i{ř##86,kfF 6ĀÑaz[=Mw$Kn@ror\ȹ~?_ό4y4=ժ~ߪj >Wְgkg_ʱM,] ?5|׶"DlCX^πv_q"g:$,dو`.n/ 0a7an#0Wan'00̛0_ `x3Պ&ؖvLo  Ꚏ_Zg}m!ߔ j,Y]7@2B6#%fsFj'[r E]􍈬(i[(y+ Q&"F u⮌xm튈T;Lw*"r""*Cjڠ,RãQG(h$E F=jx4⎪!xxhG}bofC hi*"R-*"R-)"R#SDV: ~5<5F|6uq)eY 7Gq5G-SSãYQãYUãƩQ4̌o^ joA *Fug*Q=F5wV *RBzf$7R!Ylgǡa1xQ_Op ޢL#L7,gD8 g=5ϋ!Ռ!zG_:LSh,)w9%EDJ""Ђ`4 ׬ʹEDJӊ+"R@XE3x`X^Nz%Y‹l3nFn6^-#?W폄&6YD#` -bbɏl݈xi;K\fXp ȱrٰ$K\FbEjG|o᛾ix8M(Y|Z$ q>dɨuI@>d_"p ˫88rֽ$Z"/u, ;@?7 E2SfKb!1!+1UDbŰDgto!_e1򍰼BWfX^ +*6oQG@#$Vr$ |Vۺ9o.F  mW[kD ($? Kގƪ@y #?HSM͢cR?(< O9M(zb+grsnHIa:/źR1ac): Ep).yek"x"~#P0=&"0A%$`kna-5@UBkK3ǒUpG~hhVkVGiDO&m\3FlO)&wq ǵA͈hDO婋 vq_:㟶q,mkۄFIkHe7 I\wAzvJƮnw5oҗٰ̆nXζxO7IzqTo$yӮxA?iHGM`]mғxoXqkƤH&6&G `Uݒ +Ƃ&TDNxfs [ϸ®ΥXD+X-i\)$M~HRIs 9\FU|`icP ͱ:A[RdMj@geX&8u,?5mpc5>"աV%OhXY3FdYZàcb<тkr2ؚˠu V^.ds+qwD.1$:YO/{cل0$:YO/m[ŶD'e/,;(˹2ԟCM8/d8߿kpKL'*QyQwer?D(OIl/7Cs`ljR[W>qtw?~?w`?߂ (⡇Z 8UU1H)CUG$0ڦF9 ryQ:f䴫Eð6n$y&z1&/9<(WO>'郞Et$RIB8<[㓌 yxI16pt@κ A# W.Klrese$bGC5TH!MNI8Hx9۰|al#ֶϵ&a{HwO"8N1h)?УOv8$7z&lұ-g%|*ǾM#!>0~S>C'yCۺ{8kVhj0IdcZɹ|Cl?IAU0JŒ>+!=nO[ŲiHaa&2x/垑G ھ>M`ШB,džՌe ^U4']"LHAlg0;^<2jO*Wk#:)Bij;-4ͮ%#r=V"0ˎ{FR8AߣB~nuy@'gKP ytL[`&g\\L& AI`WAww %l;& G8ʄnC-Q!Mb@G"F |X`q"/cxo前CCq,Ug#>xU t&,E ̛ UAKHm9Bo6\j@ j!,\gpƧ "h+Olp sд6:vJzFHDUmdn5xzS't/4 ҡ{7'I, T*.ЪXC145uP<ÇPM֛ bT589Uy =N7дt4Ͳ /. 28[сe9{6p+{7o1q0OU۬xZ!A i>"Yvcax _]C|6>壇U 5,bq-Ydleld,a` c>T-GtPUNVs@\ nq=Bqϱ4h'(C*J\OܮS;R!+m 0?P1>/8uࣱ59*d#Eq-(ČpcXh{Ku?g$$dYPx,=ނvtQ ـc(W3~aNr3{21Dfic) 1(-GݔK`L6yI%/K)}MksYȸ7-W@8?ÖLu99Ah&]$=fF$/SΓwD?=iP(nf{uԽ0Kp\i*q`Du E)Tr#PDlWRG"KjeЩTb(D @ێhK 䅶:Irz6S1NybUL- E:|+yNm6W@)Y_#IB%)CA.'UFjxccu85RMJ06R{A៰0yngR (-@XֵB 16訽\O}.v5a޺NnElĬz2n@]gK$}v x_ z :avGiT,z ZfeM>y p_"Ti8 i6n~ %`U .w=HSY}SR]I?dMkmUNe :$g TfGf:15 B´L= йW6F@*3H[I .3 2;J_ /3Px_[*./I3Ê'k2M&{}ꍬ^mVş$2s:"IDDHU~:q9^ ]wU+ #)~(QPU?e]~*8}.{oVm=]&ܭY lQl N#P?<#^"U}ٓvwArj='ܿdX9? EqA$qYi@ ?Swiftswift source.swift ^#!/.*\bswift xڽrF@IB>m#=`*X*`Xhz]h/Me7ٯ5juz1ԎF>y Cgߊd{Ts82CIbey0; rݠKty${)R7ݯM8ThXKtK\ ӹrוxsB%7jax$5$&;%BϷla~wta|Ga7,FH8o!Ja'z8K.AХ=JB1yR i! bx(}k@iB*xFIDu Ġ_:A'g)$N\I;OHB#]'7 b4Oh##V{<Ԛvl=g~Q]KEk%ʊi!ʐ^A" h;8{^38%-w@/]C$Uy61:" y41lΓsHhH.7$m saU䘈}>Ԉn8~úPZ:&} Iw0UA,dBzvXDN uKH y"@fS}0Md5n!myfF<%!^wt{EaH4_;A6 "Z+hYqKT$Uh8i=אFCR$ulŬAzDGFH&XN1H;U!wY^/8{>ɞN+M*Z{/RƉ,6DX71ТPr皤Ahb<ZrY4D%K(?2xwItӒ{6#El 21LCU,k h'm% /z/!"Ұ xJ|AZh2t+]ȌW'~2YsEvU>UFF{uMњJ)~ q|%DFoDdGF{PC#5ug]Uxk1L/TMH!f=97ƓJH[_yHI}ٺ%AL`|I# mcs)C{Ͷ{{Yxhb* 0owd5-PH׹`z1!Jh `HCT r m{x:(}.DEn-9TOLF3MUITI蒲*jZu1 9B]D}=֣c2f=M^XN0=˩[A^iz-7lkaz~vu}onml^4vs l%;|avL!ϟMcUjaE5.Ux=X^72el}fl~vPM:,^ԓܰo.YΦn7,7L#]ͽWZr%NƇI4~l%6Ї7U/kjs% v'%?RܚBYϘb)@!7v?Kg a=Njx(б${?lOPu 9{ڼJN$F|?:9ux!'#_U8"{W  i5ӡ57U7شfؿ Ͽj (LioR̬76/ B'?RVME~nI6mڑao~ ̈Avd[A4)s&=kA~ݣ["HQ5":(#1)֪pܻc45M-"V̓%;(%A9gϛA'L0~‘h_ I;M`']H`^f{ΊEiPu 3%)Iz~ʗ&HA/j ~EIE<.~ûȕ1N߰Gb &Q҉ܐc&$ߋxE2ټd:}׉;wHOmB/$R0 '.7@Y'NK>Q}_*a$ndk`0 ct2_W %e]}.G;H8{1ˋU(] Rבk .EOB% t-ۃPBd5} Fg4 |}aXà6г9sR5 *킪$Ew]u8}a~^E_oj5i৚7D|c V4\OhKk'5XT&פ/sp#9@^.VV'rY㰷LYhɞ+a+:$sh4^ՠTS5Yd=5{^apL_~iNUD>U1` j-(*E'Bi\b".ָ6 U\/*uzyjWlpgϟ^^?{ޮ:U=\~p=Z+DiIj"/2d1Uv6k'ކ9;+AY9IK1Gj1bVW0n6C)䠩= i5UʃF]t1mh(B7LPE0 SystemVerilogsvvsvhvhsource.systemverilogid[A-Za-z_][A-Za-z_0-9]*x=kD'` ,`0{m{1fܘ5,fYF㶺[#z%".~]}Ok\VTUJ OIU'pMNwiןSk6 a Tu]>_euNPr Uy/VVI܍Yރںswtw8v/k0)4)Cʪ|`:Uc7'_VSR#T[.09*Pڊ@gl6igQj0yJnO꺼rP\7ٛ #m7Ȝcw; S8eA(a.c^SM.&:]#7I.Yˊ`ݿ9v<oo?VuT ND豛WFy4gP$ X7馕A[$2bXMF@ ҺcQ2:L ]zGA?6)VPoo7]xGC׳3rD+B5cC? 4IV3.B b5Sp' V ET&Jx6+^Q)`au!C EF^_'eaU v#E$0wu n fDa+R= G>'G0HOEy&O A*UH_DfUPPM&8qTdq}Њ(Xo$ Vٙ ?|Ht3/u{$Ii cKQvvЃ{ n E#̶Ѯn!C/aEz(PAa!@8C֍dk̷9xcl;k;!Cʬ$oH=M9JpImgR_w:}|f>-f^ރ-a6r~G̓:<@^姎*OUs9Qۄ=%">Tg_0vЌӃ1sN$Soa~HD"01 iq7>\ 8E.[̱p#r'wrBtccb'Y[vp}XSCU,P(1V&r`M$ʵ-,ED& YcZR3j؎J9L'bpi-WxIguD WA dBD7u)HJz Ghۃ!o *[]ebEn"e\ Ui~=9~IU)֢CkQfqc]gVI>h*L+HZ;s4V.6"W3%qV#$2Uam +VyQ ս'e4 l(Y*;6G S7F;~[4_Ģk a=_gsͶLL8΂!,Kx#7.MO D΋8g0aET=68'PE044!w-g`G t%;?ȥbrr@'H9̧>/* u"@E:Q/D1_7UtcF&iq%.^izZF|}2uIaT8V\ + Gff,t/q|i/+<@,Bz)e1 |#U</kjOKa$\=pX^:ShRX!|Dn`¾ٷ*񿇊CEyMCf}#4o;kն8fL76݂D)b`[aтPV'Eb"LxrQێ|(&6kZ,Woi \ve.sBwb鲬^VeK }K,#c-. چ?T~A?NBQy Fs~qŽ ^?q|E8t\nYQ􇹧[=ݺ=:hL*>)rV*QM27&:TΗEB=P_lB8PWsY[Unus5ӫREK]C6к'[:|Do7 ~ϕh"f6A~̏?~|gx%`RG.A@.+Əe!5혁Pk !OPam酋5 pZ!ި)XՐ";ktzPI)-+TX6~X0DˆƩb`죶lκl; >r͍/Vh1;["j -ð%" pzM?kkm`k#j=/3_ssDJtEwSß-f+ +8>9|oM?n}:,ron<}&0L'9ܑli}'N/2{tb{+{^u_'ATؘRl=|<$j[K_٪ͣ1nѽiDKToɚ7l&`F,v =2͊X RJ)\a͗榞\0 fvg ^7td_zUC D )t> sR1mm䓡ySLG@ٗU8SN7ZI~e8q % R&sbk^cf(0coBa ԡᙾ.>6$|(ӄb(- KYM3r [TF-sv?ޔܥ~b<+%Ad'i^e|@v2SϱFh9^o&#O(s8 ?DH;*cب)GM l[57F~e%5n&Z}V%TNqI |6*trutruZ5 ^ XKI 5K~!%^8њ]p>WKY[jBKv[y?GρdY#6QHB+|]fW&+iniYLjbHEsj)5iey1b H ^bG0VB3úK/t~Cehj2;"-M=7Kifі-W˿{q˔lMВĎCr/C3$N-Uo\Cp?4&x|36xCvNnt?k߬ns u;}|3Nz!qQ3e2-UZfgٜi3kskIߒ7y$kߞ$h0AtPxI>7`B +jT2`*h?n{Aɟgd9}_͜bK.Sx/:-sf.3d ׂ d)i(1_j,"[&6Z=( er$ 0^iv6oݺg(ye|bx."EҔ}Ш"*4Dш$7y&=ǚBoPvX~t%6-ԡd_\4oʯʿ_~G"2;ZZn0B5w<xZl(Pd HE(*L*a gLSGz8E+ğg{1&;fٴ*6̸))M=D68?r~x3āy`{?c7C#K3+:َvG@$^ |S.{x=T~u 頲ψ؁@"UoɆ(D5p(l;8.qL $_ ICZLE!v qN?*.(ٚP; J}S麡&ke9sftS!'l/%55jw.ks^üvm)Q (폝L?w?|=\L,gy]p>ښE ) 8XiªjcMH!yMbw e9Hplt[$ A}P}.q);∮:{yq%BxU_&Gܞ϶nV>TN9[ Yp?c9z -̸k0RFC)ݭ~&ЯtpK#'XDgf_KfUbk*m]r ;>vuMHzP@{NhG x>D-!RSCVSYu j)-iIڅLH7,oBYNavigational Bar SVtext.hierarchy-systemverilogxXrD^BZO耋FGjZ.fz m)3dڙr~4qv(zƊG"ϢIIR1)en\b!, |eWH_3>R77m`=uЮ:m@۞bClp=>}7^WOGzLLm8b 8cWsarr CuA,#.pҹ2z=֩T̛CwxEl0L:v43F„\mIϺ$~FTkjvTFjOUo5e2 3<.k(ϖ3un=MI֝˪|]t.JNl7+a/%AC XE/GްLCWَ\>́9W֪#n7áWgm "s`{'tﺐ ^@{_dRdp[ÃQ4^֞󱪢 ص4u2Ō߭tup;1p$V_$A*O/a {?;?Iՠ@\M%y[&҆RŅ\&FQlOFAAIGb5 J'>݌a=;{ʖ|FtI\J\R\cˌ;!H6p:=gI>e缛mf*4%qd Yz<2GTkI?(l%<3-xq.T8:zwq>=`0&qw"b!AJQ8`oȤ 29=QҕhB(i!QOzhjHҤeY)NNaz~R=qT"XBf+@[5'r|aCu~[Ux)31!y6X a\ (cVV?YILՁ *pBl})H{}+KٓL#G.Q/"uI߭"&`6Dmuy.3zAE$H,Za{*/UlF0bX U h4N+2z#{ ZLiLjeSx|d"*mdOTN=,YVR<#REAaVyF|λ̢^/ۅ|if&mְ?47&؏+niF;i^L[$N^[ #Z8>RS]tI# yoi96RR`}Dtb"@Cel B*F1eԉ i2hkrpHj!15TZ&wAط1z [n1/$cmg>IԢx<窝"bAb;0_#.SڔmrG$5NhY%ɲ2#/%*zQE!B(+*P`Wtm-$PZŕ8y4EKךuv"|ҼGƔ{l􄉿dRe yNbv8BVs +d~ v (#Iae{!dƂ~U@q9kj/B-gKm ^tl7ԇ]_ՠ~܍5 lUZY$H}9_[x!5\[-T!Q+d[ 2h )2I@9F'n}6gLYj$33Vh T w%nU8QvZT韾dLJn;d%msE9I$x7ÆC5u%OϽ#׬dx $A"ݚ|8")[ANn_TM'I<?r5 \ ,YW\ T&?d/vݖבV8(I<(K֚i=mNc{ Dwl.[僄rG,6Hx2?]*JSON (Terraform)tfstatesource.json.terraformlxڅ gFݺu |%0(Pa{Z|߿I8vfnLĖȭ QB](Pvb*YDt|]7W{`z  Terraformtftfvarshclsource.terraform char_escapes\\[nrt"\\]|\\u(\h{8}|\h{4})exponent ([Ee][+-]?) identifer0\b(?!null|false|true)[[:alpha:]][[:alnum:]_-]*\b named_values$var|local|module|data|path|terraformpredeclared_funcs!abs|ceil|floor|log|max|min|pow|signum|chomp|format|formatlist|indent|join|lower|regex|regexall|replace|split|strrev|substr|title|trimspace|upper|chunklist|coalesce|coalescelist|compact|concat|contains|distinct|element|flatten|index|keys|length|list|lookup|map|matchkeys|merge|range|reverse|setintersection|setproduct|setunion|slice|sort|transpose|values|zipmap|base64decode|base64encode|base64gzip|csvdecode|jsondecode|jsonencode|urlencode|yamldecode|yamlencode|abspath|dirname|pathexpand|basename|file|fileexists|fileset|filebase64|templatefile|formatdate|timeadd|timestamp|base64sha256|base64sha512|bcrypt|filebase64sha256|filebase64sha512|filemd5|filemd1|filesha256|filesha512|md5|rsadecrypt|sha1|sha256|sha512|uuid|uuidv5|cidrhost|cidrnetmask|cidrsubnet|tobool|tolist|tomap|tonumber|toset|tostringterraform_known_blocks>resource|provider|variable|output|locals|module|data|terraformterraform_type_keywordsany|string|number|bool] xis6֌gnN؎JMx?`$Ƽco-@$u-=HZ~}_J  qYa?(uY瘇]jpԷ'9͢WہUݑ9^D_4h|鯊x0d0  #!pV ZJJ>M#XI1SX" }Z1( \'E1u` r)KHJQ<t=x`r%_Eap5}$Xif7#_Vq#Yst(P).ěԾJy"<|\0?pX3zl>W/^hJΕ+x,0CAB ``2gGBJBLe1QkKT 9{~daD)]~}=8w͡&4t$`!4jLܦ} l(lƠ+; >QH`5T#"p *QP%r=MbHAQEJ#W1ck牥 "'2.b#c}OgvP۝XƏT;zTXFQ}UE_&Xtxx  F']8c7Ә3rƓҜT_q|wJQ[Œwo\Z-<3TKQmg~ffO&uPlA\Xyf3plK/ZmK,>4 Xz8H# ˓E!ja=1I_k=?LwzsV4syJ}D(㡍- ٦S%ÁRE<닌V_;͙0 .GH4 ӄv(xBD 95{g̣IjLQ[)* ~<+3yɳ9ׅ**6w3Ml[mޞWҵNHĚzL;1{{EAmCvU9t飙=dUxjh̘ь9U3ˤ6ےߵL0&v7 Că2 >SX i顤h\'N!|G]濰Y 7PB$0\h2SH$Kb3|]!sn R*uE:ө6Q&M[K;b@XTe͵eizŶ9zLSHs*O($rDbխjk[rY| ~њAfvxEWxT[j_QLMc/ɧ:UR7%4K*h)R?VUcR#|W[eَ_jk/$=؟>?W~ _ $qZH5 MNDZaGc~jU!e#o;<roU<0hVAk F?#hun8 =/azugҙ;{{4,y׏uEp,8  8V3a 0,T 쎥(,mw5h/gx/eп(8Yhx95LJsbt}(2*9mKνLEC Yy=s#,bpesFnO5Todo.txttodo.txtdone.txt text.todotxtdateE(?:[1-2]\d{3}[-/\\.](?:0?[1-9]|1[012])[-/\\.](?:0[1-9]|[12]\d|3[01]))nxڵU[o0n]$.b{HZ5m'x 8t]bgD;B)ݴYwr=x %)";Zy=/㐍 %fSӄlK|$O!&J%qWkWm{UJ#DLN"W2us+niCQRXǘ ?`Zl م0f:fo7H{ PV*rSn6AR_P!ed]XOTДy:-ǘ{fצV*VbW$'1!ǔ ^cUk36/4=f3sGiǪVr LdPK JjnX"&CWN9J'b#S`W C#t}Eħe:`^I5\x 3 ˾!U ròncީU7пE(\OmD }'l#!B!uN$ Rv ٩PGݷ3/Mn<^1_aEe8=K GUQg{bڰ878g;:*/8hRo[C2kC_o8 ͕M6}dmz=gSEVd&&{^$P2ݛFg3&g mѸ5%)sACJ*"u1 Ж,5,e?oZ.N"! tʟI( rSouU!$8DbYkYR˲_ϒZ/d[&~#Cb9ieH,ZR^ϐ" ?#_ː+D73$HN4RNlɞ6ߌƣ4E:3k2`4 z[Tf̵aCeRQ͡ş'f^oo&؁6~_[_OL+tMVh;!G?;HYIdYT  47J[BZ,ʃԤВ2[,s">3ar _ N?8J+cFnr_+1nˢW%%k1b_)rM>Sf%ur`Xc٪볥|f0gmq&k\dF<MJ7 QoșQ673 oLI(K-%drTToMM*K&;)+[Բo8&AjowTA[!hIwrB05+ߙN"w%DmdI$KA04˖^瓶􃶟?9f"ҁ}'K?=S_xm+4ݞGS.F&mF+1cfs9!)ĮE00 |'>e1.|oHqA7"4WHʃvcmtLozN$^LoNΟO ?ß= ķ?kl@$sY%D.`YgK rLO/J E\Dd*Phb4?Fd)?߭0W]Qf 1Eҿfb},a\DU cEitN|jşYm7^;0mjZEgH`0 R@tM @I?.0]|O (k& 2?6F΀YY$'FO3O9A {gD1$D#->0n-mL1c"鿶5 "ѿI"Z~w)\THA2h)>4Ί h|QV$OFI~ q8L8-"3FRWdf_D]"a.vZrE1$i  L܊?jLc)U D7Ƶ,,XëHwW o7i[`*)a& O8 Ǜ]Di$hr,L2pJg]*A|2Tz 2q&YΤk:Blq&@΋jn1MB0g#aQ}D@ڇBqԄ5VwԤǺ%ӧHQ} KO7Ӎ-@wɄr9'~HM,dLϠ.SC,G+rN{S:B3&ybE &Men N*ݙl#:D$o >ɉ3d7ms҅|TwI,k5'(&=m%$qH\AtEޖUi8>-n35n<ՇbSiKOCi^Iݖ,A=Jz#bHdH?p ?ޥRIw)t\g{V_ߦA ~q@w޶:z=opȊ#}cZXo1_a iO|HEAyC:g#" }yң6鳆go<)v=08 J,vҟxݑIFG?̠1CاA z*<)2 6W)[d{R%VbWkDBCKC? [n/1(Dq( !؊-7: ' ٭ԂJEǛٰ/Z<38/[兓ڝZxuBx6V!Rٵlmb=,-`Әò( Sw%A vɁQPk3T)(X.{%)CmV״Uhz J7Vߍc9.o%݆m}痂)KNUrnkJ&h= bZv]^cp!k )j50‘Rت#Ь#.zO)8|ʼ4kk%Pfn+uSϯJ7 L `(miI t nVRXeaL(YAfH-` ,Ȓd~*TD1 &bx2<2R8[&7 j5ZϹ<#7tՈiC6rK WQIS&‚^rFLRS~Q) SŲ*Exltw7&Tb$ Vb=2rxq,'jt#DG0M|3|#0-cBVJr/0(-9qR8V)i*b)"OOF&e "\%Lz'dqВݷkFv& @2&)*Z&B5LYa?1Y ^= SsuIHTB<7ODV+ӜY^Hj*gmՓ$Ehz\VO*Q}P$#*ȃƵc"Ro[ FPro풷QΗ,%fH1q[\W06*~R wTI-]sA9hgfЃYm%~Z`i`āMN8!I bƏ}<(h+,gݠ/>hL I ,6\YTjœ5DO{GKLGLqO0@ck1LEQ`͕NS-QjYhJd*C`c$o1&W)|\p a3Ƥ̃@f7Ecg,2()yTC*@-VZx |fỌre 8d)8KY Rp P N):Bsp'l/'UT< 6u b0waڶȗP@`'\h%(]<АqӐB]B-N3X0{{fbKʩ>1luLoݦnEקGAZF\.]#wT*S&&$/5ҷrI'&5@ ]{Pذ:MJ&ż~`}6_\x1;CZ0+ ]RTV~X`ɾB )Ab{{ۑ墱:#7Y͢{WmvU9@_y &MќfkMo;O,;޽@'x@,ř~gq z%B%S?qao~(}T}i J l&ܺ;^o*;51RWFUǁg+ؾ:1gQ"Ifg.7vJ6u6fuYxҥ^elwJ - aޘeH/?^euƼ)QG&&2?hɉ4ه ,S[FY:c{~.-'JxXǕqw}e!SLcDf eu՚[ؿca|4Nxu'HxDfm)j\*zN07Ljb2(kRx.gntn 1 Oq#ٞQ#^at kVK@{>՗"Ү2V5ǁ4kUp=oRPuJnZm'Nrqbojz$ylП8־ԁq;tlŔ8) PXٔwh1HZFCF,S(([+yV>{Ƈ32rR(aXOA8g-6~B1 /l%t@K̵ B^]vB8¡{Z]؞dVy[PnH8]#Rr;,s|[t0soB. h!*c,eQ"&]4ZGH刻j6*tQh?i =ys۽IɖNht#yRӅ9L_[~=&ղZ{Hphr YլL`psL{ >jl9z4q4` zArT z~Nx g 'X[~+M36Gw_hd6v6,YG l,K23(X]2dXF;a Ӹ@x 0?kcQJ2L[LCOT+ c|?7ݾz8;!@~Fڪ+UirdoӉ$0n,Y%ո' nz4Oϥ b_mgUgve> c7ț#xWx{kX.iNwt Y#cӼ?$OI 'paQ[=2W2:Dg cML]m'F`p^m:'p#qhĮڝŕ~;~2kd ˹!a|{ћ J( i9=7Eӛqc}%^NIVNM,Tʻ86-4Oۣ׋.W}nCjGHNaM<}mf v˷ot;z!:|§{|R5v;S^h&^'e47P|Ӎȋ(Vej=&-m/n##ࢿߦLgYO6 GjeN fVqkvOfdm2\<q[\un:^ ˢr x3㹭g}o~eWr!*@<]{cǎ'voۄ]Lqɮ0 t4-om<ˢ[)w҉,Io`4ǫ γZ]+QяUb˃XDQ|DV$.Bͤw*x J~-[~D-- APro풷QΗKmf 713p($EM`& OM@S* wṮ8NRكZ4eaCAY ƎfPBV ŪM#$W?a@s΋] r t T+Ǯ\^Usis{]w]~:Y?4D4[1TtFF xj.)<k3y1~z>:ZuWji~{ 9n |vHT %C6NԒZ4[SC^#82&*Oz,4Nmkbb Ý _s~vdOGtΘ"iS`1eS.9bwwf+J`X4uvw 0GN%SpCy%uZNu.T\Ŝ%F`! H1DA߃" )4XC~Q ZyHs`dj8V1'mI*7nE!*b { Tq*A„=vj!wa?p1 ѷ^ KLFy`cnOl sz߭,i7%x@y#M@Jb&6<ՆrV| 5$`53i=AuЏj:.:^M?0tz'LACruGX0-0q z"% oӜ Tn;;]osW"IiDqSE%lz`h bZGsOIA a("_PEsso2o/[!qRH76g-) z wmrvNx2C`,\BTH_pm\ MᜁT 3-Ph7V.;@Sݍ `=s]D?~k%Ss#lI_P'-dGuюsZ3XW$ YQM Nw|AK~ӏX Z)6>`r ox] 8(!.&+ױ`?QnYg?وF{y[l doK M{lBy,y1>$6b78߅QFe=&aK''/A驏ٰϘfJ?YƂ+l80*{Gt.xCe0isq'.2H̆P޾6Fٸh\c:Oݞ`+Ws) z(?N£*ry4Xb`lgBh38%'z cdİmIaM Sڤ NUb |e0WmZPVOT$B Uqoj8^'OVy?j׻IN_*[FO/L}Wp#!I yoeq/P0uݖw6MVM>gˣ&p:QJYWldZ㞃d֤RH%eئ`oZ-Nϲ|8:Xʲm\]h-',-YKg,ʒZ-c-pzPsy> ⇨) ߭sӟu7ϮGk6V#{|' É~xEM `bLBzCC5wi}mrz}AKD%VBP94ܺjLTЩ|`Y!^(eY-;a+Yt'flIN,,F2V8[ݠ2>wӎW~1 kwUr-h3##YFgKd.xd3M+LUnZ.nY02GMx}KJt"-mӇEC.ž^X `)$5NGf+F7.fmF W6ݚDĽvj 1xIJu4ZASiaQ_% !NR#愖GK̔@O!XBԩM&RQ\BT3Jjd8xٖ #צɸ*92:3d߲\tZkEq$F%# (ama?O\z–Z6{`VI&!m(e~j$*W EEJ iY;@N<8L'k̝v7š^rn"f"P@<v|}& _\ {,wc(Y y5᏿G~k$zfVkH[MM؊ qņ0Ul*6L SQ SņgÔ\$<>T?28Z,rSVgD:틩 &,K -Vsa%{ ͱ-q<}Rrt }eu5J!Z۠ÌIG0^>#Y5t>5UY_فYD]Dsͺq(&v?Uf٬ [CsBbRio}[Y\a#_}fZWݑXXl?l&t H7]3]mBU~}Ic `ۑPA`y FiP- *HwXaZHf;{eqq .ȃn/TBlG3&O[Ilw) ^ͨw i`R~ ʶ=vyhر8Y1{ :.pLTH\NȌ {8e@e5K'G$.P` ;T0+ڒ~ Wx⦧\ - }wQ6HV4=qH`s쵵ݭ3?[.5W6:␥؁{{K-&ZkB<,/'5헠^N tZx[0ڧ]29!c.ERB.)r U.5 U Rq+ B!@u w[;IKѬxr^$OKS)W(_4tug\~-Z& *o gi^(Cr{alUBڄ,Z{6JzbpIB`J\^Ar7b##e Gf| mK&,̉noP~,-v#ǻ\V՚Vؒ{ms%O[)D?,_a N~w!KH؟aK&}+!vey=s4:qC2!~!08\pξ;~ڄ}qs}пX{ mff'B&"IMTn-[ M>x 2ٶ[8ɲI9 Dl0p5Ä8/3ixq P:GţkOύ$)e^W;kZLzŤWLz8Cjvr=̃{9Cq0Gnn'*N`_ïJ gF/(v̴}2/)~jO~XX!5Bt0TeToôژrobO[4〵Ϗ-ev;{ DCE-~G{㪢+dB&;@{F0Szی΍,%gnmG~,poO:E8۰[ۡj;䣘򲻫 b=q\Iޟ@4ZCRZW7W2s5ӋxUK#tE1ES>:R<%.(&i)^EGGŊq2sp%]'? niq!c428v(W?SImm]B*n(n(n(@-n(n(9Bg9/2_q&2vC7ڌ Ty4c<4AWtj* vfg"'zl s#YjhZkŧ˰ ~CgrR5J))Bjl9+i"DQ7[^knzAg}2deyW(tY~UϮg5+VZl]` (ӱhf=v;8?(dׂ^}c*%Ogt$4;!z x((\hJ\&5PZXŮ~NW?ybJ-딵{DXg(Kl(,k<6<7h80#֝p5Xz~ 5=k;Tn7H'es$*hk )1*\|tӬI :qn]2 `۩>p(&}?}N_5'_P*;`G1 > "08aQBDq=^NWo?<7AM!f:KV Ỏk-߿>l,ik wz4Pq)hbn"KZ 6k4.o-,0l{1fjOgMɒO̵G∳ᤣ]*U6A<('xs/o3h1}$`=kwEh>7AhH~H}m.<&U /#ڻ|bj"bxeN bJķi5l3[x7)#uq@յR> YYՐ/U6C<'A%қ[^ :ˑ &QqT~4zO7όCbMIױxL`/F. K_qKYwYl/&w[,S!Ra|Q Bx}~-=/@,b!|3IޮxʫogĀmN){6eqKEߠ`!t2ȋ`+c1ojc#Mf:M2 i#!% Gfcꛐ3PVӭ7æ I`Vn.ux )h =7+l؃AI?xX7n;$e% bm$DYԹs/dU!3Jx;B շۈ`Em-J+P:Jfl>aP {:!1Iëbmf6@ WdT~ԶjSC*71[J$-@2~m5 _&P" GgN 0o@LKV]_|٥k./B._`|ˋ_Og/,_Bx#v%ǻtSU_6LRFlo!za۹m҂.+FuM7o.I+z`CBW~l{Vq:,j缠ʃ1}S_bI7<6,̚KԤSX|ga= Mxjr5U`5Ioc,+NH{%N%aunQmzp$*N1 >ZD1)RVUNVӾ/wAmXaGĶt1?:”cV;-׋F|5{t\7b?%w@P=Qyo걉iPFTXbGttw:=? OJ*>sn{8XTuBqjE{s`~sG{':q2.{6 @>zcnhK5hjYm5AUm㽻}II =vK}_>ۡZDit89uW 5y;),Sqk\4쵘NfM#Γ6D]qߋB1(lvΖEt]DR6^GlmHDZ 1%-hF3VeN;*xp&xXVcEoŭ.A0 Nw{Ϩ$ZWLҾojU5݈9\xC?Μ:¬^z[L(DN,ox].WӢevnaF@|t4w+U^;n̩Jqd x7,/(r!Y 6JAD4pБo09ʩ{ A1WVK{6Bhww|BA騥w;MĔu~"t( I8' Ą/lӛh"SVoCmGꘌ]eF$~ AkDfſT+)2:cKePmx"~x}&^R#tfX:QN~KE jo7 2rXr۪ ۃ?)`|!V[^s{n/|.nl_, -$/ ,=.`ڋa8@ @*:jh <"f *| lJKY 3S )&.$N:x1};X$8 DC9XV,c^sn&X.~ɡ';8#Y+%6 B" -9O۩|zlڶuxd^qR9ڥ~VM •˧OW2QKKۍο0 f:Fhlq1%4q`AW_}yQLno燎@;C9k]-l#Z4kk %j6u0)hlK:G(BKbtDDh1@ TZ}+蠢 O(FCL mT$nyL*~̣$:nkP^ 3Je4&qk$(\69m^zz}]LډU^J+_ *.][~B;:WSy{ k'ĐGFR'F\ScRZ;-6 Fnb&HzEǿNQ #\wW@c;-2 MU9J9u)W U]|;  񋺊w1d?2B46^UE}=r;גjoVSVՀuҿ; 9QNS Sݹ"I2l):P0 s3b, Cᡐ.12#{u?+yӳ$y=f!S$Z2pKT߸PW/C|$5,/FsGE`kyupWU:*yށ^'n+];sw+.i @`">[FV|˗WO]t3K3.|x҅W1/\bQej7-K疢<K^Y:t4奕ŕӯ_YZL,_8,T.]ZҀ)x̵ӯ,,B.^.;w3"3gO/oW.aK.\,Fhr*x40Y赕@" !SkvYz ?>,x)lk΍u@G7Ph,d8B9 , ű1-<.X;&ħ6< A tzMt kVϹa+E/Ɩ ̳ Ol[LlI=CPY %6l!19=6h3 Y`/o02:@*@; /r2b ߿F^Uw? L ʨ@6qnl >7`Ǣw(ʔ;StVh<v$Ӣ$hx! P1@/&<&6¦a%\0gMС i\B~H92ƚ {xMh#`ft) Zlm{ 7F%i0] )66TQ`+"b~G A?&ܢm@gv߻zM?:!cX^C9s" S_A[hA V Ib:v .\,!vׂ׿NKqc!8 N?k0o#CcU-BTƊ@݁,~{Wtpf,p: / (H,Hy۹ֿ6*jޖ at"A ÖPEoL)C ⮋6[Hm>'Qp6n+Hm0 R -l.oPCvjol0G&&ce>SXm< |Ijq&[~'}}x`[\Wza(L6alx#0>Aw֐rDX>NRT9$1BRuT>Z2>xĉQvK" M8|ꛠOXǙnrvH9ѾdCg-:/ ɻ0\~ƍx7.a~ d#ЄOѓ W ,H]‚ l. ݀h]{ FEW>H.4\\" zc8DLqow I=YI2\}C2 lp߁ Ҳs6i,3p;2g$N aۧmm4֤w=A=T="Р iF)7C3mig~9wvmA€\.nb> R LMT} 7`yMځh?* Ҩ}ztpr.@Mprμ`?j_ô ,Mnw&6.-u$IϴtF7mKXaUY˞}Ź)0^zJx_X *>p4;Lľ's5 \zUm6płKaq2 `vڲ2;N(Mnkٶ7tI&+p7ijõ`sV kuGXmu0惼 +Aem0@[ w{oԑx8(KDߒ DB}!r',x Q2.; gdɏS`x/2b:1SzА=x7MR/xjjɮ]K]rLʠ\1he{Dln` |ir   {0@ 8q\3r4 * *B{n`LP}ɨu˦˩@]@XX:KE-ḪF?ÄF |AI.d+`MY+++nC0wp WfVi=yӠAva'Ev0@wyL +W;lɺyO? T tT4E"a,Ԥ'/3(' gݰKq I!dzSG ~R29,lQ4nqj(̺fg*r2p˿ ".rZ;- uo#uvPq /$@PBe'C6Dy~O [Cw AAb@bl1U8M}B-\IBݛ`6#KOs Vٌ d̼V0ZAɲFˇ1:de{`KpZQ4]>Q's]`:6p3+S` X,t G4aC)33 ,0{Ж ZVKA'i@=Hw!@ ɂombdܦejp0I|'Â@1'"4L fI! mkq)`sH" & ?0$@}LK`o`;|K<jtHQ4(qhF/LsZ@/%" cY8FoA=)yКI_bO@=3Xq^jQ qVy*G#ըr;#_em$>=M{`.f/c"mcQ~T̅Ubzuf8y1/J; yJ\&0xX No SJcaC¨0V)UmfQ#lQ H6#(!ejXǙOAV7S9=ϡLhE ~V]nE!6h/ |_q@orT/Ȇ`H I-^i$(±rT0@P=Gzsޱ)=JO,7EQ<*8sZEhу1c6fÓ6Ԉhj\(mkȚq/BU As:A{: ץiC=z3Flռ| ȳ Կ +0jE.Ƃ4xFF}0w Q`&/n]n3yʌeCHKOb_w.d,E_bvLqV'}{jVoG!tuwL.dAfjUBH^ ?}](wє\88UIDJA?6zr"dt̯ƣiVaECj,Bel$̡!#2s`:eJ"Gi.S̰>qD3nǰNga]dJnz|Bq)uRbEV-E#_'7cncjyFұ>Ut/!1KLҾ0/g`yQDḌO` T*3󀬅x=Td#qsH!6q>;3U (kFѺ=vfͅo:>ힿIquoDmh1ʑ()L {[.jqf f8t:qR8eRf|(W,!ЅE& @0#TYo_(.c{gpT4 m41dfcgB+Y@V2 ZGN(XZ'~ ;֘G ‹ǒOBSӓnTj$^Nf+ri|m FCбJX$C幞=0س@/p}e )AbGoO(P@gV nWpݛסZmDjx@ww`Uatro L4qJ:YH-w^}ɒ%1K%K%%~_Z+j7^݋e9I1Y8bf_ Csنɬoz<֙+ ,jeQ@(2TjuxUyw`4\F޶~sOpwvhsG2O'þ۷Z w_ˬO5br0BCUbNP_/NײF?N1nႏd 7.K+PǵķfEM#Nnog2Ko7d|6PA[ss.0]c8W.Lf%@΢>b ]46}>)TӁnf_doUʸiƸ*Z0ĮUx;JN87P]JL WxTltJmo:IK¡q3\9`L7CjGߜLג?"j wKI~HX %I7YLn*. ج[y)H/[(e*39fd1?mvdzm8u~w@@'N{'{1 Ak i:1-׻-1Kdz:G<68Go80I̓k{yO앚7jLh7r7WssRP$C%&& vum[7J7:];y?a'*wSWr7c49;nESo%9!UʇI+4T3!a΂ sϢCOq:^q:z:ZޡRlmQ\rHw!Z>IZ}Vꛄj& FYVAlD05+1~RJc|#(QΗ,+%AZ0A <3֏sCg}:~G_ݹ^ =J7@H o3nЗ[]H?H*WjkGjo.ߝ;n4!~B߻.?ZbKQc7 _?Œ|H2aġ+Z9 |ղД -PU*LHbLRTpHp0= 7>VY>Id]|VjҺ+h8Vsw7xbb\3Yp Rp:BSp .t8?:pIxRuE]eTypeScriptReacttsx source.tsxtx `Gv:!igW+XKN@ I.HI+ H4ӣGq;;8vN{8Yvn;c;NWWWt1x6$NWWիW{U/٫=[:NyJ5.WlvJ˅v3_Ptfn{ұ{]ixmfeLfŞEdn7{>'VVFI}˞OE:>q*uq{>+p7{ޯdweO'~O$Ow+E|$"su|_I^|:"=ۆ>Z:bޗ)hνm={M>5%*/6ddFuV=Gړfc3LVH鑡&|=n$N"pdY:@mtQXӇ2e=9lWk[ <%.q +4(OJ)|"r6OƧ=NJŧO?8F4:.ht>.!Xkۋn9DmYZ8+}Qh<_c|luZG*uttH9OӕݒEϤH,p$EbA4F"Z$sW䉚"D6Ѧ]^HN+pbrZʏFw<^JC3)%Nn3f5NGvHGYMhLFͲaw:Ng5gomRD)h4O n8*06o\,h$ӈ*$k5A,iJ9kY|^&;}2B@k7]2HnU]jnoL,hsR9aΏ}Q9dM&Fͩdef{d-H&D=e-F`0ǹ9D3?6hb$u ~8"ԶsU5ቦc+"wZo+ `EyFkT"\ڠx#M*Oj *qYfχc E(,Q_+) 8./I-~+Mj1.`HX,)5" UOhtfB%xXUb?2qOcZnnFr/(7oaQYr,D?}9{~("W| 2kݵ>4|U$3˖ȯN^@5`Uڢ&į͔Muz<" c07f(M%r R#ߚ&(ϚGEA}{ZArZϱ ; 獐J$L"9!_O'G|:1tbtb#9c0tE {>nJ?hZ;ua',$y¬k:{rK6lͿ [|?6р 5]ty8f:W4I!r^%DsO75: ƽ?x+7ۍ,m30O:#s{M + XG6?Q\ cڨb %:Ft'' ;xgJ)4c|O}?M8]a<Һ.L{h@.RFaש5i Cp@絑3VMڜ =yS"\hDVJ"]; jr{Apޔ6A~<45 4 5>(ϓ7ԤPr8:~i-ϳw/B>O?4Ad^PŎHK>Oڜ$,N >3/Gi[:N}V5Y@m֚X3h}OC @<S=8mIx"פjDLg)߯yK/2UL}€b#`#|f3tZ[ x* 'Ӛ]x ( ZL7e{.]([pXQj+.,PP|bD:` zRZ\;y]c~rBp^+D5enHEgj>Nd+\5GI $1,]rb.9ȾW|kԥ"#:yNn 0U B@MR rm`x ;lƨ>Н56l"lExFD Xk.d Or1 WxDOꉢ “TLBϱD6o ^&Z]_4JEm6 q %mvP R=JY˾ }+izt;pFb)\h2N,SI~ 9,WC7qq(R wjmDDXS&vG$5Mg5!ʰ%\YO'2=Ads+&vC$)T=&( !kτM h%gR "p9N<@hY5̙Au&%`0%˳LP/27AD< .h9^@8@@*k6o9qEˎU,KŜ޲OZ||$: !̘#c!,gAmɂо@ԾuxBerm6䲡i?nhg E7# P:O`F#iB^?w\`N&wӖ["[J0H܂HDU`F#ᐳ[00͐ r^2ğ3' !fqSHʑ!TR[ ^Gݘ:{vM BlʏtNx yewj$n#z} ezyZ+<?gnZ 6[]+d¬b!f!_Lk>֏ֆ%rbۜ31!,DpzUsӿ1o#֝&NHh|wTwV=3`%ڤـ  Z]7ĺV"A׀IٟԀCCsb8)y"7oAx#%%xK?()bA9+e:NbW9ө-x;*g7!MUVHfa>5h۲?PZ-Bi!GFV+8dJ$lQdnZf[MNܱc}c ne+@Gm8v8urYu󮽁&yss(ZꂯN1ҳTwE3%>b'G(əDN0l+% {ۦ?lλa]Ӱ{ު._v6z MaqŹf ؙ Fz+R+5XauvlNN\nd@5Qw6-G, 1EO)ve++Ö//NX̭r]M.< o1l:~iRÂLJk8?ϸRD8,@/^Q[U޴+^:dMEq@JmR˜ ;nS`8=EՃX (8Fv 9'vsYrmULE,oe(*JE[J'KݨPsRPKA PS.Y*eU>8D:cށ&zBk#!tBSu54Iu䇔3(.<5=S+,{D(bVY jIVX a\i0Z%=vę[Љ_r8풻ZΔ\vP _$CzMt{g IêE FkҢ?A6Z(Q9*D3PiMG.s1ah./[~*;P*[SP阀ëAtS2OLP)~Eؚ^_\>D3+[j$X/D3Ah@fI|X 58VO@U9)o}YEHT@_{k%{lEHj5DJTD:&g\ePjѢ! !(J ?!> ЬUEfZ$eL@f";M'1?MV*;hN֟ZtMҋ 'r!!3xt3ek(j,_.|/b\A͗r1_.y9V?![%w*~@&ږrk3۸4悔;@&n ]g#T>1Ei$1Q%)A+ nO?tn L-,'<(=,t2V5kKDbHTړB㰞߫1uQ>"0k7Kq cvV]*@@ d !Aױ(}1#෕xًA0]AҘ A(eYjᬖ~X縿c"b,DZ9r;墑ӌ<#A;r:㿏YF(MU^IbZbibv'MzGM}Է)q Sk%7@_pl{I7NIx%C)J"\ux NJ8RN?ebfP  bi}뗤't(^T0+(m\RņUtp:.J8dyn͕fdz.vؕw*o+V2tLǴ&~kr'kz(v ➴~M&B-Ff*ew$eѱ?+N([8y蓮xTIi$E+^So;]oVOZ~;W LLTvZ7(Km "DDV4f])Ull/8ig3ar)kͨx erSd 9T]l9͐Zn ەĩve4Wj~QR4'nb[!mWᐆeTb-XIp! oJOS Gᱸ-8QqNyJDB+Ku E050q>eR"َJ5T- #}\A\)iiPgaF1 " qڊt0!0ŗ.Ҙ[م ">3aξ&ցT@S}ǿӐ4DMȭ'Wfg.hC!w 77_!ڹ%2(u n'_b NoJaf_UΉU3-5`Wtn<ٗ轆ò9ҟ׮0JtAЖjc|7pL73Gnw3Md?h4_{ YLx@/=Ǣ5b|'ʵQv->l4]LLL`(%VC|zQ-eeaC iq[GD.D->0Dx_+5kB z X*b~[-Tl( oZv)*?ۆJS`h/#鈁DyC*Q*҆ha3;=5l߁ڪoJ<(!$W"oUz׮~?8#F!J\PV)󕶧FGήa8$n>qDjèOwAFV^N9~2ފr܍Tik4}z4D.#AA噩,1jD 2 l2Yо]) 9.YڼHwٍ05$#˰F5CnCK#|.Mi;LS>mO=n ãJU>v%Cwe8M4S^v}s}NƓ^LAe"@3VE}4'0zWwɮcw|D-tPu[E]a0S*]?8 qbqoV#J>镳Et2ŧثm\nLK/2ax3:g}~݈r!"@<]d;#GRpe/n!5=(B=Ԗ}uza3sH;GRuJ[NXݿ q(/JhOjuX gEn۾pKa7Û}*k27h͖zڇl@II"bwJ%PK]rWKՙ뗼v\-."u'6ϏaǰI0LBG!0I x|SFR;P*[S.ư 1gLYQ+ )h'&GP`@ bk[oey}aՙ5]k%V46K]`LWj:(P+]u{  +g9Ѭ`Ʊl 4G}H`5孠/^ JŮ[:Dzf)XCMmWu]|nFq%Et;=kbRMWVDz^qOmM.(N0;NZLu%?^"gT="C㓮`Lo! !\LgܪkC?=DMŅ̄TƯ ɺ2Qk![[~V$FJ N=컶=W'JG|MɵġY=,\w,\NNS3wLH٩׃{32 $pj"F8׏%OEv*=Դ<}BT&%\ظ翾/5PttsQ]mgƚ m~\>G*,a8o2y6ǀwR!v^1N6XGm+IX2\҃(`hGolxT7b ~q_˥Zo@yr2S̍f6yhWAI2;ӽ{.ڳ.ڻ]>L6iD\ٹP5k3YWK9!\ꀢH I2iv ּ3]ge=bhZ:Qd%De4=F]aI:kꘆ )`R0#<<`gG91<E;OS9\"eǐ ߩ>W.=]{gUn0nEPL2"֪u '7"nWE*b:ٛm6[.ULͿErrFuts* 6vǵ" X< (ڤK P7N*Ek4^XGU!+Ye1aֽ.k=z]|OόN)x'oMXEGgEοF Ϡ@]t?`q  Q{2N`l&lrrYs۴]hRٰ*A/Oav:cm" xl_{C*Wt\{vCN2R*4ϏTE {'SɃ+" "eeaۚk6ڎP8,MlI\J؀ R*K{a\רv-'܎t HXe){pjs"9h#wƦ =Px*zv d($x%rǖ{ 1rhaER.\a|L {t\[,2ⶑwKW@]!Kq7.9: Q+_roE 4C %`ql'iiʈӢ4=i2hԄg ^6hB7{҆dtnM,J>=*oڕw/\nĥS׮KqoXD J#a ƗU>* : j4*pZL _gR ;} @-ZOz04Nm+bb:;JE0qƧCLl$iugAiSfo,2K_0\âC~not펁2R;)Y# z2Gv!*Q4;e~@HlXݧ`#i6k] ժ:&7w/ W] C{Cy@^A~}('J>Rhrֽ&*A„=NB:ă2W@am2/lB\=sz߱f \<|vX_OR~ex,'Q,rT3gϟ-/_!Sۘ(s= %]* UmuPLu/If xѪOB2jxͮ+ $iDKtن-Ɋ?{J*Lcާ.ͮRo6d45y$W\*c2Y Ӧ`!庋ְɺkmns-o^̀3'ssS|k\ x8c%un ,k Qj{ ղ;CR*L@09@… @QgݾlI_P AdtѪsr_$|GO hQgMvw$ lAJ^ Y 9PLmm2 9_!?.{3֑uS+ʀ|YvV>ʘck[B.<5mq Ns  TWu3hK@fR / ? qbWzFE=5Z-oal9alalal9alalal06v,%1V)xSRaX<>קqYL]sw Wg23 Z`lE|/sPb\̗9r1_.EJrjdknڥ@e8?}Vht2}'73Bq$b?}";ė7<.UwE$}0~eC05ruP8,.qfL ;/?n&=N=[%'4Hk&yl]lJtZ02!(LߧޜXTt۩a_\ jcR奖MJΩ뷍U/Vj03TZRUQw{W݁,eX:Kd[]iqj譋 @{:cb 3XZiّ>fU'>~ d<'L7X;̞0DOY#st uhUj›gkY a(h4)XP4SXݺ#ʨTOi:Q_=ЉB jdP ²RvTZ VW(:H1%ődXi: vmuh˓a dJ4[ьC\!!QCϐ#{ n(>CeHo C9G^n]6gSo+v`$8h=oXT^+s+ah6ԈuXWB#HBJ/8 g*/b/&fI5S:A }ٙ_Y*HV:#GǞ3[ : #D aŵ,MeGLŐG Ȅ ;؉EqXVH}֭861ɰ ijvxxP!.~;/R/ִ7y]Sa,^q6\6 f-T!0 ٦<ӅcdHaCYC u4.坁S1`_+cزb)cD25 YU.CKm:]^I-0~[YjXes2Xgj〫e0(Q ÊX[~K\sw,[y Qy*I%7nK)qkeNbl̑SL vn@8Kd)=H3F)miq,psluHywfH# c0*ƩEAq;{dIZt"]An:kJ ~vMCӎI)ܕ'wՓ{nbO>'.dVh3^{VN\EL pk!;0eC=WÞ?h(C7( w@}\{9`~v G=f髉NIP.oʷۀm@6|P (Ķ 7> ;ܚnNɄijdo9 nF7`Yz<&'V uwRSj6ZCͪJz; _&+})T/ꊾc(_ V/ӣ]Sg ŏv $?k[aR+wVN[1끲/bz֛9뚞ތ8}aYo?!W Ⱥ~w Cf`G32j]0!]G벥̢T.  N;}yqE9 &;SHw$zT0 Bn[i:¸jbDp?3 c%fOЉq,'>;kЕr ;bgKWlF]Heڢu$A4^vz҅z ˄~#uf AA5+@v`1 gw[qKkaz7K O{La%|Ϭ0mCR#uLMԥ1,SK:H6T*٣S^[L$Ipl `!6ȮE/AtiE鸋 ˲jҁR.uXڣgϊ+Rx^MMZ=>ofd&Y?|0~edt:0 QܸarKqo8NcI,]z'q=F'ƅ8+?/+^M;|CdٲN+ؠhL<4- ƉѴneHqC=4!H!dRw?7[Zǜ9gV<ֈ-_Jo:[NX\+ݾo վʽ8f7$߯H:tY\mj^FG/{s5;#$96q@~ T D9wds({r<SN}ds^61:]K^b^ݠ< s#')=w6eQk@s,M{o9|tIF_pNDΰ:j np7 sJ4Ro𻉇Rs_+wW6ܣ1r1-h=$$ט-^<%l7q D=~yK,rbgEI02 J_ޥj3Y2%?{gQWR5_HyRl@$fŐi wIS9u]B֘qutM_2D Z8 $%a6YbHҏ !Qnc\+ԭ^;5t5ތkۅ^-c:On!aC Ϲ'}Y :TqΎt ͘#yck:.<*ԶB فUlFroc/JQd< ƥh5\ks `A50R n{=DQh7,5Cp}Y̴HD?Ds5E)Dl&70D45>[dr8iZ>Z% 6I=IYԐ @_̀hţ3V55/b\A͗r1_.|/ÈYm@ݚ8y1 AFI%|rԶB ۶[K. p_IE^Q廴]Zwؑ3MFײ>KwI/B!eC9445qmLQ`'\w~ L+_-f :NUlԨ t9wRȰ?0qOa ]ZpuD&t6v4v7;HxxXː͛۩ HqQ!}X"e &!yLb)`bOV75 V|3tg=?/e<ݕ oc΀$.âbmMS@Sz~Qı8~̙;kn,UZxAŁl ijcAܰQ-O 5o%I.6lJbAF9t(9ʃ7pMVpo 8j5YtJ[1|I ,7Y^n~se,{숳X+UD6uD_qd7LQDV|nQg,&u_nr|uym_YNr?̞auovzRlYsu$N4.J.nP5YPϮ\FЗߟa[)jg w (_IQM8PnQ@èo05ep>X {~14vczeEd9 ${&J@˵A830x@jHc+]g:m :gxSG(Zsl xȪ>u ZOx=!(CEDv>ÒÂl04}g%_[| ~ltaL%MV Ịi`[*ߊ-ϻ\q 6ͧ[|mg T LҔe17fCиLd)SЍ~gw'=Dm&3@#΄kJ[#|*.J6 D=A2eĝkD̦Fl3p R=wU֡k@xo@7Bl5> <GJ֘wM]y`$z1H9s rU AU6;u0Ǟ?aR/3\V<9IF+FӒݢBpAc7$#wid^8NJ>ϦI+ ~zGm_fN C!&ʼ7.clG3!?c0LG|:ͻ u3U2?vs(,&Aa (x}vAA1<" ,?; <h!|^ b#Sj>0sӽЃn=$y<@{ X0tԄ!'L48cאftm0?+pp46|5-(#lw|K不$h}>ӭsx>J@~Wa Gݵv=^x& yqg:W1'Z{YHx{,8CQBG^AV$[iEY/zנEgNZ%uNSnQKui`620a0$9ֲ7xuǾPs T(ƕr}skdTb7d `D]sC+=iepaqCDwE`Z ė=,f:4ViLh:n8^g87@H:o qI ,'P2q?c @;iRyv4DAooR!p k@e}q%${\fLsOSٮz`n@;,]n/h8n+h}sLs"l Vae V0Xx跂ppv1 >&uESuǡ)(hvnB_64s\)Et1=[[:Ab:- zJu{X7Ǩ? kq |̊+^ /VM"7;  V yqg38;DkBNb^uX&@FE|Hg` "(o;eMF:_Ez@Ԁf7)O ۭpԲ7 v;W܆3MRT|9"(w%5¨Ew 8%oo84?omTFbOw $ S tmY}ctK:"BkRNYb}"D$oA=b ȰcAu d$M>io8ɟGjQ㵼%6$UЍz9WQ]%%刣ʛ P~;LD;R+p5GS ʛ]wZN9^۾,R^qR<=uw+RY>_ȬŹЊRg-$eY#7zkȃ?zJF(-9-5`H!LIJ[)RdP-}ME\- FE|8#}n#"BU:Z[]V@ܞp9L%EqPqI %Qi79݈ NKCmJr a&ՆDV.1ݒ@ksS?6[jLnE4ԕF8[(\V-bgaܥ.r%E_tVWy0_4pM!d|3z/*U^xބ zM"3Lŀ.\=Zt2P9m+НIOC(i: )iC;΋dUI?ALeIt} )N8-lڅkx;=J](HVOs T/ y)6I B"\0y3y`n&o< n($PG H_qOJ2y4)]k@G7PlfC Ab:9L "}Yxv^SDu. JnF:^mGçU:u45Q0H;Pw`/6h4,D0TE2#(ZCݠa3bT&H' 4H Rqom;>dȶ*Ʉ`>IJ2K]9=-lurY0 !! Ł%O wDva ϻ:1FnUw? T`ꨠ@6qt>Wm`;E{jz(U tWhg=5v$֧9AqBAMfCEM*nM>gwC~97AP1 qaGʁOXupupQd*Z"WHAX<]Gn*YnmM\HBӡK]!?X0nU6k?X:X։^hL{J~pLNv'`D;}J6?f|bTsK4.J0"Qp mhҁGCKBX/(!t7>Nq` ^&?k0ï#̇ET¶څ:6dkUK6mI_LC7ktl~}v{`F'B0\l uP&? 'uHtqCFr-6HtܶW8a@ Rp77ӹAM` 9J]yTeMrRg,qus̱cGx0u9O /e`[;Ӟo,L^eAX 2*>[|4>ל ȑQ a,~@T ﮂvW\06q?Ѿ Q{k ntz$Ο0sgsz-҅]]R?O !W.kwPC&nD|ld@LKh;y!Ƶ O56xwa(jqf"ɹB؝CAߎP:U]M|Btm"iH2XC2 lp߄ :mgO?A(p}B\: ٷˡUajemkHhr|`sV ][7ߓ~)Y]<Nn{ wUA4,9"7XqbI^@L%6)֕Lkw3lxrN\#䩳G@F%_.]]4K '~CtExQ`/-@&tPA%C@ևAEdF.!2vE}Gܫq4jd }ܼZXrUlBar+дO.B,,V';mar# A /,MY+WVC *5{iYRdُF2ݗshB1a3LGމ;:C:R4y"a4$Oj-PV@t:fAMc S ,A N: MKl7@xVviS%쯻,\}ΣNO{@r,̅ҕBKP2SJ'jWetdor.jYQe A4 8u%bI79DN~P9)AN8"@BG;H T؎ҦؘE#y>daԞxF[4vz6۔~o_%zO:ppRxTdA4K;0׹˧"q Uz`J~ ]Cw G0ATm1 ECd5bU0& +TlF2jnC@-XFÆ1Jddե}d`[[oM5]d>Q' f2< {euyZZ@q`LAh= L,\#Ø!,\{=n)_Z-5QqAtib48W@A3@517HR8({amU( !4T j }My`Γ#YaڴS>-4BPG3Kr4M12bqv8n9O,㙇C.&cq4;˃N+34J(.ЈkTn cH:ERv8uشJ$ {RD1_S(SS/Zz@4s%ǹxQ>1 Zc xWz71*3Ħ`H I%>mG8k8V}^Nō;,iw ӣ M^n~J`yf(}-![Ipj\( ِ5 &0 T%RA̱#0,p^vz`TQB $XY6h]2h0V7qzEO?ǩn(郝Sn]hSKyq ˆ,$<BF vT LE"k[ϳwJ-TK`S]@=p{xlAZŴ{T:9rzxJgoCv| "`ϖJvyGѽ8oJbbmƙg"hN,QBX* b/T-1 ꏢg-3ި3H BgoB3nmZ5 /83q[:lVzqgm6S-{A͔J] =0~X縿cʼn Q/PRZ͹]rn6vo%_Ul51/7U_֮bYE 'Q2!1_|L jx/]bW!7&*x 8`$5/d^1]_VƄ@6ĿjnCIC!!~_53 r?^܍e1W-Y$q" -Yp^z#^TYD1l+BF\g BMU8@ *`ҖkqK )f*ǯ׾闺~RWv ` Wю'OU_‡gUV?&>\fb,'X[ % 6-^LZrw/vG%nNM6Z:cBf!xFwĺt +ͻ!ôn1k ;':Rz=ϐ:_*Zy*V*J2 C첵Bq5c):3Poa{8!R\w C\wG ɉɗ`M'oPwpV;inVQ$o0JRutz6[-J_: d-xʔBՓuKή㞯Ӆxu}L>AA8m[8?w?Z8q DaQ5}Amʤ KFb ?&HƐ40^ttv!XO'N̖-nFsrxPZN6A\sq(7eKl w\g7\ Cj\ʮYE`ŲXԔOwj}/O}%u MCYf7!ȌaֈHjHz>ۥN#4Rߌ*(DT(H]~<INVE%W$d<խIj_#/Qkq^Dt.(qZ & wܤzr00oDKY9p_QØO&zAtZQlBk+4%ͷ3ErDj=M"yf,/_Ya}cP1i xh0.4-ہSU׽^(^%ܷQrWKՙ뗀r@^b`–R!FPO$Bv[z3,c̔J|3+[j$%V46KdzlR+FZ 6I]r!!3xt 0PƁb\̗9r1_.|/"[N݆ * =xu^IbV[7cG+q0ŘUa}8-b'_K|/qrP%Nɗ88E,"SR\A͗r1_.|/c-b`AJ2we6mc;'WӳZ͙ > NۂVBw+hx:h}m~"x(=g=i,ܗ {uG Nկ"$riE噀[}ҡ)0oq뼂|VerilogvVsource.verilog xYrFíL/3PShBH6VWJb?G8kim/%q-xAJZ|k묤sg嫵󥢻(ž8cm㔄CfRP=.r}EA fႢ)XЪΛ2Q4ĭqu) ǂk~iPrAE߾pA9Jv`At{O08x8Bd+[⼏#}Ǐ~l*wb vsF:ZNhy#D1u0$r_ZQuc,s+E>8E1搩ז}FnA)Vq+9{nJkNjݳݰ}z^S ʻ imz͌>L˻&o]ְ %cxm5ݾA7}\j,ȉ ׵^/ Ld36Jwb<b j@0d s,C82l=2!O | @Lj 2]$|B% b_¸љ RȣZC qA1 'c, /}1/S;(ijIgd`̰8y/%34 1h u9 8(8 `)t1,GaiOɇdTNZ@bJ7\MVvzF'BQ߇lNVX3>VՀ0ʶM06vNUv8O|cjXdCe'Kʑl"@= 6E/ j؇AVvT&b|eݖpmwO_.Hퟵ#<.K 2DZ%ܷ/?n[ hwQk;۟%h P#^5ɀ]6Crth.ŎËr;(YCp㸡ZhGLB*t渪tiOgD,ePTܣHTɧ)RB/j^XZbIZNAq"i֒2F)qzBMUWP {UEmW1B9+Ռш2 uFR%o& =ſDK VʞmA.s$O1^#(ZO0&Q%;/-FT-QI~Yj&WQ"T[=ilV4=d#c\ t֑(치Um92z.R9hp()}#X Zh\N k 2/腚`,Af_ (Զ\(hZirOBsZ/eÙb/CIRt3Tw~VW8^O/:طa6UlN h+G椌b43A9Q"uH T7xzLeZB1Z<>@yYP 1q 5GvHiC]Vb.,ay3s2@yVVV2.ɐ͛a8Po@Tb0#? ȟz;HD_OoOON~7wj:x™:>:7ꇀ}HMjCqtnĜ"GJIlCLnP>[MY#t|ⓤ)Q9rY0(knzp i.GEA" в9D#׻Xz^Zoy;͘=P^3 D0 `C>pͺ6NLXM1ʚ"%1BWH|a+"د=h:EC῁[.+ne3~l ֐7@ ">k|e.xWfqL[=}Ä2J#c!匎m\F^I8|L;|{7)/`RI `&(tw4VimLvimvimrcgvimrc.vimrc.gvimrc_vimrc_gvimrc source.vimlcxڭ]o6nS,E>l/0R JeaQT/-,Qr˵N{|Y>fܓ!~lpy>kh4wtәf:L3Y C 4iBfI"|E~h<<]&#v8,kMvKJ`OH$s#6YΛwͻYP =`lj=~J>9W^4Ⱥ" >hʷ=-kIj*0Z w@Ĩښ\.8ѹ:yf1Dj, ͵6I/ ~=_D)\i~lfk-OLxzvCrzXiٍT~gݎ%V|qh(n3/EsJZ~b 6zĶ+xiV~ui%L!Ly"6mW{U$Gj~NJhdq a%ٰ ~9`XZvayaSVo)(B2G[vzZ R)ՏӐƭXMS{rkk-P=L6 *־}h\Jln](MYM.nl95e㮝$6,yN;AIQVy*W,MT~.SΔg 8C4`7|]7I%s(qi}&bG}oر!rI%"F,f`O1\.#xd(ċ}X>;*!_]ǚ;?oDcx@F`ϰRFɆ wؤɛx70#{.Ծkobmfg}}sC1a`Z4&rĂ\aV Vue Componentvue text.html.vueattribute_char(?:[^ "'>/=\x00-\x1f\x7f-\x9f])block_tag_name(?ix)(?: address|applet|article|aside|blockquote|center|dd|dir|div|dl|dt|figcaption|figure|footer|frame|frameset|h1|h2|h3|h4|h5|h6|header|iframe|menu|nav|noframes|object|ol|p|pre|section|ul )\b form_tag_namep(?ix)(?: button|datalist|input|label|legend|meter|optgroup|option|output|progress|select|template|textarea )\binline_tag_nameQ(?ix)(?: abbr|acronym|area|audio|b|base|basefont|bdi|bdo|big|br|canvas|caption|cite|code|del|details|dfn|dialog|em|font|head|html|i|img|ins|isindex|kbd|li|link|map|mark|menu|menuitem|meta|noscript|param|picture|q|rp|rt|rtc|ruby|s|samp|script|small|source|span|strike|strong|style|sub|summary|sup|time|title|track|tt|u|var|video|wbr )\bjavascript_mime_type(?ix)(?: # https://mimesniff.spec.whatwg.org/#javascript-mime-type (?:application|text)/(?:x-)?(?:java|ecma)script | text/javascript1\.[0-5] | text/jscript | text/livescript )not_equals_lookahead (?=\s*[^\s=])unquoted_attribute_value(?:[^\s<>/''"]|/(?!>))+Kx]i{4i8m-EvIJ%ʼn6mڤ&w8c 9YptK-]ns/_3!&) V%q:nxVyJKFxN Cw%[rCIW@Cz0P9DTs.9D #i(r #rQ9%B8DE^@̲6}' NٗnۀP;IH&vf&!Չ;IpJdb3[4>id-g 0B&^i֓Q$ǒ3Gp3 i#[)+Ɲ`!N@ݚc*q^4.)f/NX)A*pnq)*'0Rl!+ޒk+!;,G Yed+؎jpR{-8(SzDX]Š$Ӂ/295%YMx6#М*a ;v _$N;ȗDQݱL=eBVWRI ?U=1 A]1!1> o-Qp.odHuд:y GfDDRk*ƽ{4 I6B,+L׃wP2 ,RlJ^K4{XU'H 22{Pw샦-z|<.)tiL%IT$'^?DlfŌ) +hN]u@]$O LjL_c%:1oZ aBoKwyM`}`]Vh'J2sik:G6Y}D i4OQvv?5:~:Gƞi)IdFsIi~CPTd&Tc*҄"10BS+z 8Fn3-o%x 5xsÀW~V8#W !@~/HeKE 9JLjkq"کfwdj12YoHgy5Q/F3qdxCR 2&9&:tJ&ctbdcF $cBY%M1QS3qdEjD ʳx8sH>fF\w4R EH9D4# -k#បEU)зwR龠u‰Ey}F=V.ID;Rs}@+۴)حMu Ͷj`>E&yOT@αBc!CnGy`=Kl1Ȝ?I*M%?KldP^V[7v /-'>ClGtBT9FƷT垸+R}וH!aӾ6]X,=LOҚ_MU­}֐QnT?w*W$ӣLL2> K.I;RZG$Ǚ.mM6+U'2XcnMSAY[g':-1F%DSSi6a `tp8BL*& s2WdHi^z݃96j @zhZútX7-&Dzef`X85d,ۇfZn;MPd9Mhu|hulµ m u`[wѿrhClTQk~uVd П}m&>0^ w 2NF]:oB?*@#50בpF۽} I 2!3hW:T7n@[*Eg̗MAĵt=U؊0}hZ#(jb._=*;WkAqEC A V`O^%aF^FS'>e?&?Ï^B;FvC$nF'X1$ХMY" uz:6뺲/Gƀ}W4ݻW[(I^қnAPFh=JbK9 u0@a}^ؗz !^ ,9$%wEMaJֻS8?g'OZV۞1|]hXfo&d_[Y(jR9S6g9. =K,||)Ƙ~R9s,W+ O~#Q&}e2FMgW/rߜd.3ʛj/ϷXFwQhrl;~ReDe*H{6m Wb^0|޹Z-^\|rw%̍T-al;fh6s1 *ahe-9rU- J{qc~;-~0cД1J4ċ2#[0:P#Z0oI= 5ڷ32Y/w1vq`I񨌞žPq"Y,䊊2Z M | yN(O|C6Ps}*W&؟*ge rXf:{[ۣm"aAoل_$?GkI$?QsSN Dbc;zl; H e-lOm%c$%E ѻ6 5`DL6-grn-OmT~QOkFv&*tȺcm߆Iw1uB#A٬xxf+0* $ƫܓ]Xb-\">*kzS6^Ǜ:R%rE.Oq6mj1qknF|[3ܧB!eά[f~ ۬Wק{z8vaD"^[]k?p8*深g跂ls+]Hi3qdDY7Q冨a}S늤.{T_UzHfmkoSׯ gyUvBf?V!?JmHJ'G_[xm o zO_`QabT،¦$s|c-nG v[R/Hg+82tj[j9\MD)/k-ˊ5V/k+B*۔y Dkϗs23ɑ rN/W\1>ڙZigzig source.zig xZ{sܶ,c'8%YdwNKtN ɃN+e^v]Ij2y|.݅fa0r=mω{N(HnRO-ݡh\|>)M<ÒZq}MNg$afGt{qRR(_qyZ2[dLC%1Ggh#>#)$[s ,MP4BkPL ?&F\-<)8{$0 D\xNlKBAJD sɂ]ψIxC4uIA3&33bzxL,=)*"%쒴>$d 6ѯ$TZpNˆ!-)Jd32&$$=ax.Qx4 ,nDV*0'ǫt6 KOYΎϋNOw^VgO\$8 {*q޿FÁubxZ57UoNi.KYZnMl!9%  2g;M;Ii`TZ|XjbZXJP.eq KxN3 UApۤ"Xn~-UOl_WՕ_5;%z"P0Ac3{}Y/58^nBwp/xg|Mld`[aR09*/qS-4*ek:brcZg/jQ8h*&?,(@4 g/yce Bm4Bvy$-`2/I=`b;OvYV{4vcߎ`F<2K0+J{ =X?Yh?B= )D {ajwKPU[[[~.F?C `7n uxtq@ ^6I"|h12,Z_`"a hat+a=u"5 A$r ]Z  &`M$l~_mݤidlR5;^`Go{}u 5ejÏpҥWX/XH9:̄zK&Y5~ҁ(SLI~wwb``4ɇB !S3 H%)qy@IUE(9$.OJP! S䈢0j rd#u|QԊ1'v!խi:xhƟiW4_jiƿЌjƟkƗ5_k45w5㋚Gs}ZH#![1[Q9bP`Y|`8ݽjCqu^5=xfZnQMA'utչ^)Crvy8:&:RKdqiIxd5ՉEߪNVB(m)Ya}`Mdfضfzt{ ؗ!)j>]P\_om<,_GXoiޖ}rPv"d㼧y@gS %0dM%4;r-9ib 8]bH9% ~#qY/xA /q*=ŮM3 =bҁ;ޔ<}&pt%/Rҁ!A5|.SCK)P aGavp}NR(pfs⤭ƨdR%ٱxB,QmֵP5ra[݅#}CnT`pkù&8 )Ǡ_"+*Qa7p[]Z-߼{{[lc!7ց5.x ʱ\X . :#8PBBn"4rb4f;^rHSv յ-ꓯ5<o>P8g"?mNKoU[oPy{Y0)=W\(3RAr2!Nɺy ΜdLPT›DRm~.+=RRF7Ƞbg@eRuKMԀje MANԁ4~{9ōNg7E]e$qIqa89e쇰S?<Шnt:.p8E$ؒu(§s5PXbbSoZ([0f۽Usgk3|rXCzC*:)Ʀ<dpڅ+f@|L Command Helpcmd-helphelp text.cmd-help absolute_path(/{{path_atom}})+/?allcaps_argument_name[A-Z][0-9A-Z_-]*\bany_word[A-Za-z][A-Za-z-]*\b any_word2\(?{{any_word}}\)?color_code_begin (?=.*\e\[.*color_code_end.*(?<=[0-9])m)ellipsis\.\.\.home_relative_path~{{absolute_path}} leading_word[A-Z][A-Za-z-]*\b long_space |\t|\n|{{single_2_space}} lookahead_40 (?=.{1,40}\n) option_break[ \t\n,=\[]Wzw,c&av>a[o;#hCS4gMOi{k'yp)Mt{,ٰ}ưXgXfO <kiN~lkSa F/4u=`x2l- '^֭([l][ U\<<4m ٰm`tHp,z۲a*gn5o8M1y!taĐa{7-MClpy+@q; (ppAsSj~/8n:ܾbSj0z7lm8nNd[.ϡXS&zAMQsql\|Qy $١S(㾄O٤ u\,z=#ҵ:|s]\cZC y\t.˼G@%a2Ks i<ؚw`j-&yi7.2Kv,l2y.#lL,=4Ҩ,oY,e*#dCAq:F[gN:8JW=-9q"JQk$Wo{3j/![1KO)tVK<($+ ddOz U)0$.I 6Q~ aMmDޤ;AVr݈C2rr:ʡ $ʭҲ,'N|63PoЌ(P?_#SN$+U@rYΗȄ>K.pJ2l-9l*? yz-=H=M t:m4Ae }9xf Fj\-`T"5 " ;u9Xbby~ XQɰ!YnH%i>]i,Qvi.eJw*29?I)AlOi8m|bj)L Lpm'-F<ˎ2nd(TF R^CdCh]'e'Ѱa#617l;R'um[^1E!L D3H35a8R{~ ADgAL15'Pl%= .jzEҠoDIh/ d\ʤ(KW ,JJ0a%P'-4{)Wi5^8Iè%\Vϛ(VSvaMA-ƙW6ݳjG¿ODDZ|D'L[) 7g%-ٸZ>&‘ w"XZ: #ZnK5$'H8sg憾8w5^P1Y~Y"~}2YFݡ|(v2iEn4&pAQո hL3e u_X-hV{T%٘,ZM8EךVP >W4kF# d#l(HckzNĔ}r-JѿXM`zbnA@=bժC|{/Θ/̶\w_kJ"t9Ƕ+.7^a,v gnuplotgpgplgnuplotgnuplotpltsource.gnuplot xYrܶ8uθc:rH%NJFMfIяNEYŒ]xI&ArϾI"/ЇpԮ$gL8@spn^[,E axgΖY]p.-ǹp./ǹpY\N.3Ǒ(z1 Y9&Znj dH&{-KZyhfom R)y-d !}dzg*E(LjFi,JcզjՖq´إ">t3ԧ=םgݽs|?jZ(8.c82!IAl^*'.zuHxB. dkKvv+EΉr0\]y%,N]YశK-r/!SL{EP_O57k7: =vԮ&CRƄMm=K٪pt-˧Dɼ=4"Y&ƊR@91U=j4Y$*I,U! E,2%\GE4d T4Xt/RQ$<3 ABjȢ{Z(s6"'Iكh<X *Ff&$٨BiaBTo"Ǣ"LD~)W"cM2-JAYJ-TiR4"&Jʌ*Y'pE,q^d}iH(YBaIjzDJ`0:V2 jIĴe5(M;j~ڤ"mW랛uP6oœjHISG\Yf$"Cټ!!չQUs3e4r3b1u̺X"#\Q4XJ^)7Qp;W1i4YZ|!.]hQZ"99/ܿ3Ge_čfE weesNK(1\# pN$nh!U@3QC.$ b cݾWƟ &"Q/"JC\Gڋy*+^-~Vc" ]Dp&MpM:r Xݍ6Zy6jT$dDq^)pemƳ?55z|+\ϲ}bG=QOvuz?U*XW3z慆/Feq#_Kl^>WQеܻҋMGMkH`;M;qI%˪lm"[^v]I~]v؎e6릱{D8 a{C7˂ Q t:l%E繰P6 jW;nS1"#?$χ ·(5.I ׋a{CmZvR S\iv+B*:̈㨀p5W댫lm5A2 Αܺcw7훶 Myn3zdÄw?~w =b 55ja"kSy̺h‹x7ǃxy`P}UFT k  ܏ go |S`|QjG}֘n,?IJW¶նu,Tτ NCp= }4U̓h>VU8G<^Sʡ\,/q\CuZfO`KRE{h BA9Z!Bh\䚴Wᱢ,-ɚ7#8Y+GBUwR:2RBau qc~6bpN 5`֖*kd~OƱ&'qІ޲~g$ӴsKXMΫWIEiER̥[~$ϤTΐhxz6!>-"?w6f7ŕ3NB و炘ߋ,4~TA-n. <(A3QÈtzw7VEMy%rcH 9&>õ w!%rM &ȵ`@G?7POkVsW;jZzW[%gլEnLnl3?g*Œa' D]+fJP8{3pD1)iC\|2bOŰ/6|W3x3x ͓U/)Z:9muR?oxO T oUb\C;ǽC=F1SnBKz -8ʥo#N֫YK |9uIH>eb/#-ص];Ia_帧KE#{ afOƏEk츱W_ϟqf4U]Sχ;c@U ېV2D)տ)'jDv lp-9bM g`LLHY`yKao3Ym:c63!nV2ɫLsQ}POF[IP:( !uY ۙ-|ʐjFG,OjݡZLLJ )gƩfw^>yy>L:lۇnIZj}2 2_uU-ʮ.wcg9+9sxVo:q^>/]hBMN?CLaa+L2!;#%^+t-fV!rQ;WkzGnp'qy}yobB{ɗ͚cqnfԍF61;O&Kk%GE33:p'Cv+wZs?!ǰQkg},L77ʇ%n|= SOv}"Ke:TK> y*[_ '?9T|S8V @ߵv&.uf;q%v|L*f!,>p0C;Highlight non-printablesshow-nonprintable whitespacefxڵJ@S+ z| vģa#e6KݰP=;|<)ݭ1ݢ8|fMbͰٌ#gO)26]3\5,=wJ5z>P PΏ6g6K|h0͒DiLrJ4T="LȂdck>7+=4J.i4[rۿϏVz`؅~OU hJ;\@-i@8G(;~ZBJXwgo@-M/,YYt&'_ZrX,-V::(⇌OR5 cW?&{~ ?˖}~lo̖:IϏVe[yHH$QB$I؀G %u5NyR2 @Lu&!@F&T(>i/u@Nzl tb(qCW@$@@)g}N,3a~j^wg6c<@g\@F!` ۫u^yzq`.վߺmmm y tׯߺNuG霸}K -&KHj]km#h9wWȴC)?b! aR̴.#QD8r_<`f@~P- 8+*" J.f Known Hosts known_hostsknown_hosts.oldtext.known_hostsxڭVN0NmbW c4 uєMLc5CwFکiqbs>K8+3 "hj=a?űϹqWqܚb~3@Xnr{mwOӘJ pxcb'1-WSGn-߲ErV{5N0Dkb3ȁؗԕ %Ab80BM*i1spA<aG< *ʉ|v og%W l(|el5*sɽT;7 a0̂e սS*azݾ]W }PH8O"[kWqT+ ^CY;3JGZy224>8eZj;ѯQCP8g[ ^kq4pڹx3j;k~\!]-2NbdHZ35 l-E,oM( @3[f @E9P3PXڲ.8֮,ޤAte~(1 2*|N1a@ME`BͶKʃ(>9k Private Keysource.ssh.private-key#^-----BEGIN [\w ]+ PRIVATE KEY----- xڵj@3oM7e)]J4)H)EGԉff7Mm$@WS4TexPLB@1EJ!.zt=2 wV8TqC?AR/m.EUJuN5I[wSu~{mYpe SZ$Ss+H~ SV?Rxud# Ҋ|Ğ m6e4$,Jx SSH Commontext.ssh.commonipv4+(?:(?:{{zero_to_255}}\.){3}{{zero_to_255}})ipv6(?xi: (?:::(?:ffff(?::0{1,4}){0,1}:){0,1}{{ipv4}}) # ::255.255.255.255 ::ffff:255.255.255.255 ::ffff:0:255.255.255.255 (IPv4-mapped IPv6 addresses and IPv4-translated addresses) |(?:(?:[0-9a-f]{1,4}:){1,4}:{{ipv4}}) # 2001:db8:3:4::192.0.2.33 64:ff9b::192.0.2.33 (IPv4-Embedded IPv6 Address) |(?:fe80:(?::[0-9a-f]{1,4}){0,4}%[0-9a-z]{1,}) # fe80::7:8%eth0 fe80::7:8%1 (link-local IPv6 addresses with zone index) |(?:(?:[0-9a-f]{1,4}:){7,7} [0-9a-f]{1,4}) # 1:2:3:4:5:6:7:8 | (?:[0-9a-f]{1,4}: (?::[0-9a-f]{1,4}){1,6}) # 1::3:4:5:6:7:8 1::3:4:5:6:7:8 1::8 |(?:(?:[0-9a-f]{1,4}:){1,2}(?::[0-9a-f]{1,4}){1,5}) # 1::4:5:6:7:8 1:2::4:5:6:7:8 1:2::8 |(?:(?:[0-9a-f]{1,4}:){1,3}(?::[0-9a-f]{1,4}){1,4}) # 1::5:6:7:8 1:2:3::5:6:7:8 1:2:3::8 |(?:(?:[0-9a-f]{1,4}:){1,4}(?::[0-9a-f]{1,4}){1,3}) # 1::6:7:8 1:2:3:4::6:7:8 1:2:3:4::8 |(?:(?:[0-9a-f]{1,4}:){1,5}(?::[0-9a-f]{1,4}){1,2}) # 1::7:8 1:2:3:4:5::7:8 1:2:3:4:5::8 |(?:(?:[0-9a-f]{1,4}:){1,6} :[0-9a-f]{1,4}) # 1::8 1:2:3:4:5:6::8 1:2:3:4:5:6::8 |(?:(?:[0-9a-f]{1,4}:){1,7} :) # 1:: 1:2:3:4:5:6:7:: |(?::(?:(?::[0-9a-f]{1,4}){1,7}|:)) # ::2:3:4:5:6:7:8 ::2:3:4:5:6:7:8 ::8 :: )ssh_fingerprint#(?:AAAA(?:E2V|[BC]3N)[\w+/]+={0,3}) zero_to_255D(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9][0-9])|(?:[1-9][0-9])|[0-9])xZnDNPVhliko\UQA BbSIkm>9Ixdȕ<Ԧ4l G52= dC#1A1۔i~3&,\PF}F\X}9̈́l # 9B*\eUcx+->R) jgQ 9>2kEFfY>lD?]|K5sݟQb稫y{t@Rs0Y I<+!kܠM)IbVO&3)"gp.U!$p\xd1#Ųvo)[CgF G珢3Y j3^XZ2|6 /\ɏ֤;ymac$4\7&oC :ZP8ے|d}ܤ;%V2-ρ }ܭ+f5f$V+XVfm@;U<4.$Ky;!C9KUs,p.$W݌ع A ܨ}^sk{T!yhk̯@ k35O} Cd -$;JⰨ)toIOkaq J}=䐰N_¢RU8PjSU뇊\vvhd s$e&8bG,[A5'Qٛlҡ}}`g0- K="IgӃ޿&\x`$XDWsL#ɂ/8V>eaPkT;&uN0q@B&80rU?;Bs(j/nO*hjv3E ۫ocQ2;ٱ̎ev,cFv5蘇S X^RKvˤ]&2iILw#iӓzcՃpuΨ5tuΜQT&.?~Яa$gK\e mn- ,ywuzbܑ\S;V^VJ1yx>~tWMz-~] g,0*׌keuz^}w,UzyށIvT ?Z̋# SSH Config ssh_configsource.ssh_configL xnFR:q&i2~e 5W3lj#FpkȵDZ|Vs?$Z8(HH{fgU~^@!DZ1"C4O+֘%Cj!tYt *7})}A? 4Lspc p}D!Ga3jglFO3bZ\NΗg1[q9' j93k{.O!6S]rP Ft}2l&b`ٛ*yT . 3]l#dT" zZ0p-=܅&AmC j]YİCgH ^H,iȶ?\_@U ɰ<Xg5,7//yg05oLx{4qbEbp&pZ! 1ǂ_ F:d߼ - > 4HT),5絲"hwsSP\ZAVʡ 緊v*8Ee6,]ds{$O~Τw:USlA#AF.mr.E\jf S#_"Nj`[fisU~Rux{jUO}1sĐA7L|`8L5\up"RQ;/13p;4){=l#jd$>sP͉@%QMmҖٳlzo=(g 7I!|>DW 3,4V 3d8H26dL#TLE$MaI2<ש2kP> ~11 ]Hعb};P6VeT!gfrzwsijHͼ4V&=EUq9Hr>(?BdV&s 8<;m$fŻwA`8(/;ܬ# }}|Te3 zp|E#e!'y&̓(5 }pmA۴tlOwWXkNl#=Lo~ 8ڢ= ~Lx!ym9}þO<'|%.C>F~9&t혔kb;t./ 8ޚ*jaoo.( <1S?"ћKMJV`-:&%6Ȧd2] j' =ô* c ~vr bjD}VA@X|YYP[VYTp$Y$aA$qK骥n#}g Al|T#Y([NT^AUJ jx̣R`?" XfX*^˲x+O #$L؄ >ϔޖ"M?k{TƖW%u XR_h8#[i@(6Xᱝ.M,\T[ [/Kg%6yM2~E2>'.k۬SjhT9V%6}餀k9)LlTTv'(f']rZ(~<{q хv( {սmAރ@V`X\98% .tlpUe\ $fum W?FqQVߵ:zX6W_(dUE,( HVi %))=+2:heF#[)Q '+pӂ. ib1zHd$8z!:o9̆N n$H[圖=Rg+?8S HnnKI%Ɣ`8 Zvb[Q0xȮs&Kq~ZEf30 0K墍t2Z2Cw?|ɡ4X *kģʻC'3 .Jj>J^VH'RE©K]$8[D<]rveY #j%n9Im>.bkQE+z| /QZqE2^&\2~g Y&ʍ$!,F8F, :A,-.7d -nLb9rj$^n ׇz6t SSH Cryptotext.ssh.crypto}xڭXnDiK B !V`cJQnr $0]9Ymlo+%%5ό#Ŋ2s;眙A|R?8a7ε"aNee *s\q^)j|̸i$eHb*5t~I^'; Qg |R {%ǘ }bn-J!j}W.{[֓aΣY|Ve*Ju}EHs5ܫc=r ,'@-tsqcZe 8uua# hRo^~uqyz7W9t*7K|nycnT2f:S;RsQu68|tyJa@თN)Pz$X(ʁ?]8 H 4F HbNj(>`SZ4/^V+z&n8J2u ̏BlI5<_jVeIEMt "MUKY*f\( NlrNɨf spP8اN%[[Mk6* 81 445 5_zK84o臷N{Nfv?'\?릉f y:da_/d2-M瞿XVV6N9p.1 id+sȔyOSt2RkLYNLMG Ծ UV۪&0O0DkJՔ\i f3s 6@b4u3]f+~ #<4D[VJ3:,ʼ˻kgo쌗g,Qf, %]'Ed|io,խڪo'4\"Ug QM,sN-E3==NﺮvS; uDsz/ޕ$+P *-` L,LMyڕ;c"RnpVCkhSM6vHjYEvJR$ /!GK  '#(e.tMC"6@dsɇ3٭ё.in.i%DD׉m aV i۸_J%W/In}wHZS4ZK Y `0ڶoƪΠwG܊ot=&?եYU">44DbI.ik$zB__"l PCʍ!ƐdHkŶ@7xm֤ӎ5iCgz^_weo<3g3U:X;x'dz@$:qaRrLϬ^.~MK~?, SSHD Config sshd_configsource.sshd_config all_options\b(?xi: AcceptEnv|AddressFamily| Allow(?:AgentForwarding|Groups|StreamLocalForwarding|TcpForwarding|Users)| AuthenticationMethods| Authorized(?:Keys|Principals)(?:Command|CommandUser|File)| Banner| CASignatureAlgorithms|ChallengeResponseAuthentication|ChrootDirectory| Ciphers|ClientAliveCountMax|ClientAliveInterval|Compression| DenyGroups|DenyUsers|DisableForwarding| ExposeAuthInfo| FingerprintHash|ForceCommand| GatewayPorts|GSSAPIAuthentication|GSSAPICleanupCredentials|GSSAPIStrictAcceptorCheck| Hostbased(?:AcceptedKeyTypes|Authentication|UsesNameFromPacketOnly)| HostCertificate|HostKey|HostKeyAgent|HostKeyAlgorithms| IgnoreRhosts|IgnoreUserKnownHosts|IPQoS| KbdInteractiveAuthentication| Kerberos(?:Authentication|GetAFSToken|OrLocalPasswd|TicketCleanup)| KexAlgorithms|KeyRegenerationInterval| ListenAddress|LoginGraceTime|LogLevel| MACs|Match|MaxAuthTries|MaxSessions|MaxStartups| PasswordAuthentication| Permit(?:EmptyPasswords|Listen|Open|RootLogin|TTY|Tunnel|UserEnvironment|UserRC)| PidFile|Port|PrintLastLog|PrintMotd|Protocol|PubkeyAcceptedKeyTypes|PubkeyAuthentication| RekeyLimit|RevokedKeys|RDomain|RhostsRSAAuthentication|RSAAuthentication| ServerKeyBits|SetEnv|ShowPatchLevel|StreamLocalBindMask|StreamLocalBindUnlink| StrictModes|Subsystem|SyslogFacility| TCPKeepAlive| UseDNS|UseLogin|UsePAM|UsePrivilegeSeparation| VersionAddendum| X11DisplayOffset|X11Forwarding|X11UseLocalhost|XAuthLocation )\b7xYs7Ci!+R@)wq~Mg LgN8d]d-mW$딙Y}Үu64?t:"ۧ>+%ύt|({1|J'+g}yX1#\J3CLqD* )"S=>(}9fHxǨOƇ|Tg=%^uq"a!Ifts9sI,p,W1~H2 qqdup[nON:,0Ĭ۳jƈێUij }q:Hhz ɬ {N*P\x*'CZ|Cj}2,[iZN~~M}ԏ áxAe#y I0j*J}#Z,:BvʗC."h<X=/p!Rb|n&XD~>,'\M( 5$ Qe(HnC(Fc.#qgՀ1N" i$Ҳ`l7YL:i)B*ܔ8|I 넣A-`q^}(-QWdK_"Ф"n~ؓF4Nˈ'9ށq1@\{ >x7 1 `q+b6ئb4q$ȾbR30|=q(6(;bX7y(q¸2FSI(#m/{DّVI[4u=0n.0MSGn!/A7#c^7!֌[&[cZ/"XI$UPhXј08kmv:Z}/{1$JrD;Mmr*.bq%!yb‡_L0n};țt|uw֙/2q|p.`' E7*M ?pio(TIRl1 > Dz;G"ta5:;`MWA ?ڍ-=D.Q4[(S?+wϞAm4XH XU޴xYA$SpW%rfT,P VP3jYAP.2GU8ciGpV- ae\;ٶ?3l)J;fiL=k|O>:גhm!:2J}1PW__.+z? R:ԌwQ-D32}*W㵯*FH(5|XqoL\ľ9g9; d]gCrŢT2AN%0?5?V縓' qcs_7ivD߃j7+̅)0.^?Fždj[&G]ՠA =2Y(hINZ'%˥Ӟyj(Lm<֝rّ]ub^ʱ'dtUr@mJ;bwuJ}*c) 䡭=7=a 3lE,F侍ԨlɟKaLPgx6]+eV[ӦcꈓSof":jf .{;z##Ł(lʛ_y 85_ɡ/;twЯo/t󼚧(`Oܝ5Ż?nP۴w.oЯX&ަJ%ߧvL;mT6]s7y9Ob?l[8-էsgky4/~o_V 8}7#3VCu)՝%VkN KͬPQr v.+~YVfE\Tҕ҂>kոU㴳~'$ [θCpUf2aqsۓu2vvi'gyf01 |G>9á^[*gXgX} t;[#q{M[Rט{(co^syslogsyslogtext.log.syslogxڕUK0nܗT]JNJ…cVNc&N8[?+k'qܤЩo>\wFtpxʈ;v1ˏ[Ҟ9ËzE'L%m`?[ I`"ݜ^oe;ɑeNe~0jN]DCJ8SUbΙ卵-G5j9jv!;1{DSP±-Dp_Ի S!a *>cjDS_4 hrcA䉉fiOW+4#٠e%%jD/ӭwmx>;pU@3 #SN\^u)HCZ t^Um=n58f7JE[奊/g٭ʻrKBokb>01n!A6̂u~Qa1\}ytȩ~wIuhF)pJvarlinkvarlinksource.varlink field_name[A-Za-z]([_]?[A-Za-z0-9])* identifier([A-Z][a-zA-Z0-9_]*)interface_name0([a-z](\-*[a-z0-9])*(\.[a-z0-9](\-*[a-z0-9])*)+)xڥU]o0MӦ=2}$J_$BN0ld&&_VC_V^x(f*S"gyv!nR1dɦ/G(V=paN X,y>x_( \X]ZBru?%C,/$_Kivӟ=|pC3 b[6*|ْ'Xgd[Rf >.`}TqXf-pcΖsaNQEr^IqcL*Ovl4o GN00r]%q)#YZ};-)`s|nɰ%.} jK|:ikfyTR ꥺ^WAI©5,Я}n:st4wM3їх .]gǾ][ؽއpg_:fl'{=2$,#tw81[}`a =+2@ؐly@>WGSLwgsl source.wgsl1xXYs6č8Gdzi"*8ikZC֧"(P"UVܠ?OY]@ 3Xj |X,togF`DSgewnÃ@^eo\S5.&)S:\4aJwW ." f+e+py7<2C\29^G ЌZ!jĕ₈]z9+uG>EʼUE9fl"7_|;Ҹ]yu`sݮI]UJ0_p=!)2t eBny G31rqq#FUi- YF_?ߨߩ߭Vb2#_NEl5cj(|K,vq>=uܿҲ%ou3j:p󁼌#YU PF41JXRu'\BK#DSB,})jDW.]A1V=Ohi:bZ={a͉mH_`wNrzLԡ4wq6!-^T3.)7 W=$Yd}i])”-Bb:ݜJK  U 1P\ľ{ J?WlCǀ=#l >T&GS8Jm2lJ&(`$]9VQ(Bfľf vE䏶70%)0v̿((y j }( x-᪇$ŋ#mIAc!1ŭ"ɣ$4^XLʍ.LdL5 As|LNOyéjk+ n9PǖW0hn ( M,bXm" V[`%?YMk3GhYy&%+GCTYzPt`2MBj$*ͣ4|mQu}1p<%<ᡭj X"<2QiD o Ү եH:k(\mqv8)ff[IINJ|Zul*nNR.ߜ-oU(l[야FM2D~]r}qRi@#u-KVir!(fܐ Jpw;$ۑM{${/-A~(@Xddm'c֚xLZ Ӱ֜li;o.&]r}M7zp. Bor#"L"7Pp OO}w7q.N/DNq+y1UpNӂ 6 8 57w$]p-G\R/RqOm?H//home/martin/src/bat/assets/syntaxes/01_Packages/ASP/ASP.sublime-syntaxM//home/martin/src/bat/assets/syntaxes/01_Packages/ASP/HTML-ASP.sublime-syntaxZ//home/martin/src/bat/assets/syntaxes/01_Packages/ActionScript/ActionScript.sublime-syntaxX//home/martin/src/bat/assets/syntaxes/01_Packages/AppleScript/AppleScript.sublime-syntaxV//home/martin/src/bat/assets/syntaxes/01_Packages/Batch File/Batch File.sublime-syntaxI//home/martin/src/bat/assets/syntaxes/01_Packages/C#/Build.sublime-syntaxF//home/martin/src/bat/assets/syntaxes/01_Packages/C#/C#.sublime-syntaxH//home/martin/src/bat/assets/syntaxes/01_Packages/C++/C++.sublime-syntaxF//home/martin/src/bat/assets/syntaxes/01_Packages/C++/C.sublime-syntax H//home/martin/src/bat/assets/syntaxes/01_Packages/CSS/CSS.sublime-syntax P//home/martin/src/bat/assets/syntaxes/01_Packages/Clojure/Clojure.sublime-syntax D//home/martin/src/bat/assets/syntaxes/01_Packages/D/D.sublime-syntax M//home/martin/src/bat/assets/syntaxes/01_Packages/D/DMD Output.sublime-syntax J//home/martin/src/bat/assets/syntaxes/01_Packages/Diff/Diff.sublime-syntaxN//home/martin/src/bat/assets/syntaxes/01_Packages/Erlang/Erlang.sublime-syntaxU//home/martin/src/bat/assets/syntaxes/01_Packages/Erlang/HTML (Erlang).sublime-syntax[//home/martin/src/bat/assets/syntaxes/01_Packages/Git Formats/Git Attributes.sublime-syntaxW//home/martin/src/bat/assets/syntaxes/01_Packages/Git Formats/Git Commit.sublime-syntaxW//home/martin/src/bat/assets/syntaxes/01_Packages/Git Formats/Git Common.sublime-syntaxW//home/martin/src/bat/assets/syntaxes/01_Packages/Git Formats/Git Config.sublime-syntaxW//home/martin/src/bat/assets/syntaxes/01_Packages/Git Formats/Git Ignore.sublime-syntaxU//home/martin/src/bat/assets/syntaxes/01_Packages/Git Formats/Git Link.sublime-syntaxT//home/martin/src/bat/assets/syntaxes/01_Packages/Git Formats/Git Log.sublime-syntaxX//home/martin/src/bat/assets/syntaxes/01_Packages/Git Formats/Git Mailmap.sublime-syntaxW//home/martin/src/bat/assets/syntaxes/01_Packages/Git Formats/Git Rebase.sublime-syntaxF//home/martin/src/bat/assets/syntaxes/01_Packages/Go/Go.sublime-syntaxM//home/martin/src/bat/assets/syntaxes/01_Packages/Graphviz/DOT.sublime-syntaxN//home/martin/src/bat/assets/syntaxes/01_Packages/Groovy/Groovy.sublime-syntaxJ//home/martin/src/bat/assets/syntaxes/01_Packages/HTML/HTML.sublime-syntaxP//home/martin/src/bat/assets/syntaxes/01_Packages/Haskell/Haskell.sublime-syntaxY//home/martin/src/bat/assets/syntaxes/01_Packages/Haskell/Literate Haskell.sublime-syntaxJ//home/martin/src/bat/assets/syntaxes/01_Packages/JSON/JSON.sublime-syntax ]//home/martin/src/bat/assets/syntaxes/01_Packages/Java/Java Server Pages (JSP).sublime-syntax!J//home/martin/src/bat/assets/syntaxes/01_Packages/Java/Java.sublime-syntax"M//home/martin/src/bat/assets/syntaxes/01_Packages/Java/JavaDoc.sublime-syntax#T//home/martin/src/bat/assets/syntaxes/01_Packages/Java/JavaProperties.sublime-syntax$V//home/martin/src/bat/assets/syntaxes/01_Packages/JavaScript/JavaScript.sublime-syntax%l//home/martin/src/bat/assets/syntaxes/01_Packages/JavaScript/Regular Expressions (JavaScript).sublime-syntax&M//home/martin/src/bat/assets/syntaxes/01_Packages/LaTeX/Bibtex.sublime-syntax'P//home/martin/src/bat/assets/syntaxes/01_Packages/LaTeX/LaTeX Log.sublime-syntax(L//home/martin/src/bat/assets/syntaxes/01_Packages/LaTeX/LaTeX.sublime-syntax)J//home/martin/src/bat/assets/syntaxes/01_Packages/LaTeX/TeX.sublime-syntax*J//home/martin/src/bat/assets/syntaxes/01_Packages/Lisp/Lisp.sublime-syntax+H//home/martin/src/bat/assets/syntaxes/01_Packages/Lua/Lua.sublime-syntax,U//home/martin/src/bat/assets/syntaxes/01_Packages/Makefile/Make Output.sublime-syntax-R//home/martin/src/bat/assets/syntaxes/01_Packages/Makefile/Makefile.sublime-syntax.R//home/martin/src/bat/assets/syntaxes/01_Packages/Markdown/Markdown.sublime-syntax/W//home/martin/src/bat/assets/syntaxes/01_Packages/Markdown/MultiMarkdown.sublime-syntax0N//home/martin/src/bat/assets/syntaxes/01_Packages/Matlab/Matlab.sublime-syntax1L//home/martin/src/bat/assets/syntaxes/01_Packages/OCaml/OCaml.sublime-syntax2O//home/martin/src/bat/assets/syntaxes/01_Packages/OCaml/OCamllex.sublime-syntax3P//home/martin/src/bat/assets/syntaxes/01_Packages/OCaml/OCamlyacc.sublime-syntax4M//home/martin/src/bat/assets/syntaxes/01_Packages/OCaml/camlp4.sublime-syntax5Z//home/martin/src/bat/assets/syntaxes/01_Packages/Objective-C/Objective-C++.sublime-syntax6X//home/martin/src/bat/assets/syntaxes/01_Packages/Objective-C/Objective-C.sublime-syntax7O//home/martin/src/bat/assets/syntaxes/01_Packages/PHP/PHP Source.sublime-syntax8H//home/martin/src/bat/assets/syntaxes/01_Packages/PHP/PHP.sublime-syntax9^//home/martin/src/bat/assets/syntaxes/01_Packages/PHP/Regular Expressions (PHP).sublime-syntax:N//home/martin/src/bat/assets/syntaxes/01_Packages/Pascal/Pascal.sublime-syntax;J//home/martin/src/bat/assets/syntaxes/01_Packages/Perl/Perl.sublime-syntax<N//home/martin/src/bat/assets/syntaxes/01_Packages/Python/Python.sublime-syntax=d//home/martin/src/bat/assets/syntaxes/01_Packages/Python/Regular Expressions (Python).sublime-syntax>L//home/martin/src/bat/assets/syntaxes/01_Packages/R/R Console.sublime-syntax?D//home/martin/src/bat/assets/syntaxes/01_Packages/R/R.sublime-syntax@W//home/martin/src/bat/assets/syntaxes/01_Packages/R/Rd (R Documentation).sublime-syntaxAS//home/martin/src/bat/assets/syntaxes/01_Packages/Rails/HTML (Rails).sublime-syntaxBY//home/martin/src/bat/assets/syntaxes/01_Packages/Rails/JavaScript (Rails).sublime-syntaxCP//home/martin/src/bat/assets/syntaxes/01_Packages/Rails/Ruby Haml.sublime-syntaxDT//home/martin/src/bat/assets/syntaxes/01_Packages/Rails/Ruby on Rails.sublime-syntaxER//home/martin/src/bat/assets/syntaxes/01_Packages/Rails/SQL (Rails).sublime-syntaxF[//home/martin/src/bat/assets/syntaxes/01_Packages/Regular Expressions/RegExp.sublime-syntaxGb//home/martin/src/bat/assets/syntaxes/01_Packages/RestructuredText/reStructuredText.sublime-syntaxHJ//home/martin/src/bat/assets/syntaxes/01_Packages/Ruby/Ruby.sublime-syntaxIK//home/martin/src/bat/assets/syntaxes/01_Packages/Rust/Cargo.sublime-syntaxJJ//home/martin/src/bat/assets/syntaxes/01_Packages/Rust/Rust.sublime-syntaxKH//home/martin/src/bat/assets/syntaxes/01_Packages/SQL/SQL.sublime-syntaxLL//home/martin/src/bat/assets/syntaxes/01_Packages/Scala/Scala.sublime-syntaxMQ//home/martin/src/bat/assets/syntaxes/01_Packages/ShellScript/Bash.sublime-syntaxN_//home/martin/src/bat/assets/syntaxes/01_Packages/ShellScript/Shell-Unix-Generic.sublime-syntaxOh//home/martin/src/bat/assets/syntaxes/01_Packages/ShellScript/commands-builtin-shell-bash.sublime-syntaxPO//home/martin/src/bat/assets/syntaxes/01_Packages/TCL/HTML (Tcl).sublime-syntaxQH//home/martin/src/bat/assets/syntaxes/01_Packages/TCL/Tcl.sublime-syntaxRP//home/martin/src/bat/assets/syntaxes/01_Packages/Textile/Textile.sublime-syntaxSH//home/martin/src/bat/assets/syntaxes/01_Packages/XML/XML.sublime-syntaxTJ//home/martin/src/bat/assets/syntaxes/01_Packages/YAML/YAML.sublime-syntaxUE//home/martin/src/bat/assets/syntaxes/02_Extra/AWK/AWK.sublime-syntaxVX//home/martin/src/bat/assets/syntaxes/02_Extra/Ada/ada-sublime-syntax/Ada.sublime-syntaxWD//home/martin/src/bat/assets/syntaxes/02_Extra/Apache.sublime-syntaxXF//home/martin/src/bat/assets/syntaxes/02_Extra/AsciiDoc.sublime-syntaxYL//home/martin/src/bat/assets/syntaxes/02_Extra/Assembly (ARM).sublime-syntaxZO//home/martin/src/bat/assets/syntaxes/02_Extra/Assembly (x86_64).sublime-syntax[R//home/martin/src/bat/assets/syntaxes/02_Extra/CMake/CMake C Header.sublime-syntax\T//home/martin/src/bat/assets/syntaxes/02_Extra/CMake/CMake C++ Header.sublime-syntax]I//home/martin/src/bat/assets/syntaxes/02_Extra/CMake/CMake.sublime-syntax^N//home/martin/src/bat/assets/syntaxes/02_Extra/CMake/CMakeCache.sublime-syntax_Q//home/martin/src/bat/assets/syntaxes/02_Extra/CMake/CMakeCommands.sublime-syntax`A//home/martin/src/bat/assets/syntaxes/02_Extra/CSV.sublime-syntaxaC//home/martin/src/bat/assets/syntaxes/02_Extra/Cabal.sublime-syntaxbJ//home/martin/src/bat/assets/syntaxes/02_Extra/CoffeeScript.sublime-syntaxcE//home/martin/src/bat/assets/syntaxes/02_Extra/CpuInfo.sublime-syntaxdM//home/martin/src/bat/assets/syntaxes/02_Extra/Crontab/Crontab.sublime-syntaxeE//home/martin/src/bat/assets/syntaxes/02_Extra/Crystal.sublime-syntaxfB//home/martin/src/bat/assets/syntaxes/02_Extra/Dart.sublime-syntaxg]//home/martin/src/bat/assets/syntaxes/02_Extra/Docker/Syntaxes/Dockerfile-bash.sublime-syntaxhX//home/martin/src/bat/assets/syntaxes/02_Extra/Docker/Syntaxes/Dockerfile.sublime-syntaxiD//home/martin/src/bat/assets/syntaxes/02_Extra/DotENV.sublime-syntaxjK//home/martin/src/bat/assets/syntaxes/02_Extra/Elixir/Elixir.sublime-syntaxkO//home/martin/src/bat/assets/syntaxes/02_Extra/Elixir/HTML (EEx).sublime-syntaxla//home/martin/src/bat/assets/syntaxes/02_Extra/Elixir/Regular Expressions (Elixir).sublime-syntaxm_//home/martin/src/bat/assets/syntaxes/02_Extra/Elm/Syntaxes/Elm Compile Messages.sublime-syntaxn\//home/martin/src/bat/assets/syntaxes/02_Extra/Elm/Syntaxes/Elm Documentation.sublime-syntaxoN//home/martin/src/bat/assets/syntaxes/02_Extra/Elm/Syntaxes/Elm.sublime-syntaxpI//home/martin/src/bat/assets/syntaxes/02_Extra/Email/email.sublime-syntaxqG//home/martin/src/bat/assets/syntaxes/02_Extra/FSharp/F#.sublime-syntaxrG//home/martin/src/bat/assets/syntaxes/02_Extra/Fish/fish.sublime-syntaxs_//home/martin/src/bat/assets/syntaxes/02_Extra/Fortran/grammars/FortranFixedForm.sublime-syntaxt\//home/martin/src/bat/assets/syntaxes/02_Extra/Fortran/grammars/FortranModern.sublime-syntaxu^//home/martin/src/bat/assets/syntaxes/02_Extra/Fortran/grammars/FortranNamelist.sublime-syntaxv\//home/martin/src/bat/assets/syntaxes/02_Extra/Fortran/grammars/GFortranBuild.sublime-syntaxwU//home/martin/src/bat/assets/syntaxes/02_Extra/Fortran/grammars/OpenMP.sublime-syntaxxC//home/martin/src/bat/assets/syntaxes/02_Extra/Fstab.sublime-syntaxyG//home/martin/src/bat/assets/syntaxes/02_Extra/GLSL/GLSL.sublime-syntaxzM//home/martin/src/bat/assets/syntaxes/02_Extra/GraphQL/GraphQL.sublime-syntax{U//home/martin/src/bat/assets/syntaxes/02_Extra/Groff/Man Page/Man Page.sublime-syntax|C//home/martin/src/bat/assets/syntaxes/02_Extra/Group.sublime-syntax}I//home/martin/src/bat/assets/syntaxes/02_Extra/HTML (Twig).sublime-syntax~C//home/martin/src/bat/assets/syntaxes/02_Extra/Hosts.sublime-syntaxA//home/martin/src/bat/assets/syntaxes/02_Extra/INI.sublime-syntaxP//home/martin/src/bat/assets/syntaxes/02_Extra/JavaScript (Babel).sublime-syntax[//home/martin/src/bat/assets/syntaxes/02_Extra/Jinja2/Syntaxes/HTML (Jinja2).sublime-syntax]//home/martin/src/bat/assets/syntaxes/02_Extra/Jinja2/Syntaxes/Jinja Templates.sublime-syntaxM//home/martin/src/bat/assets/syntaxes/02_Extra/Jsonnet/jsonnet.sublime-syntaxI//home/martin/src/bat/assets/syntaxes/02_Extra/Julia/Julia.sublime-syntaxD//home/martin/src/bat/assets/syntaxes/02_Extra/Kotlin.sublime-syntaxP//home/martin/src/bat/assets/syntaxes/02_Extra/LESS/Syntaxes/LESS.sublime-syntaxG//home/martin/src/bat/assets/syntaxes/02_Extra/LLVM/LLVM.sublime-syntaxB//home/martin/src/bat/assets/syntaxes/02_Extra/Lean.sublime-syntaxH//home/martin/src/bat/assets/syntaxes/02_Extra/LiveScript.sublime-syntaxE//home/martin/src/bat/assets/syntaxes/02_Extra/Manpage.sublime-syntaxW//home/martin/src/bat/assets/syntaxes/02_Extra/MediaWiki/MediawikerPanel.sublime-syntaxS//home/martin/src/bat/assets/syntaxes/02_Extra/MediaWiki/MediawikiNG.sublime-syntaxE//home/martin/src/bat/assets/syntaxes/02_Extra/MemInfo.sublime-syntaxG//home/martin/src/bat/assets/syntaxes/02_Extra/NSIS/NSIS.sublime-syntaxR//home/martin/src/bat/assets/syntaxes/02_Extra/Nginx/Syntaxes/nginx.sublime-syntaxA//home/martin/src/bat/assets/syntaxes/02_Extra/Nim.sublime-syntaxC//home/martin/src/bat/assets/syntaxes/02_Extra/Ninja.sublime-syntaxA//home/martin/src/bat/assets/syntaxes/02_Extra/Nix.sublime-syntaxF//home/martin/src/bat/assets/syntaxes/02_Extra/Org mode.sublime-syntaxD//home/martin/src/bat/assets/syntaxes/02_Extra/Passwd.sublime-syntaxH//home/martin/src/bat/assets/syntaxes/02_Extra/PowerShell.sublime-syntaxO//home/martin/src/bat/assets/syntaxes/02_Extra/Protobuf/Protobuf.sublime-syntaxS//home/martin/src/bat/assets/syntaxes/02_Extra/Protobuf/ProtobufText.sublime-syntaxT//home/martin/src/bat/assets/syntaxes/02_Extra/Puppet/Syntaxes/Puppet.sublime-syntaxS//home/martin/src/bat/assets/syntaxes/02_Extra/PureScript/purescript.sublime-syntaxA//home/martin/src/bat/assets/syntaxes/02_Extra/QML.sublime-syntaxD//home/martin/src/bat/assets/syntaxes/02_Extra/Racket.sublime-syntaxB//home/martin/src/bat/assets/syntaxes/02_Extra/Rego.sublime-syntaxM//home/martin/src/bat/assets/syntaxes/02_Extra/Requirementstxt.sublime-syntaxD//home/martin/src/bat/assets/syntaxes/02_Extra/Resolv.sublime-syntaxC//home/martin/src/bat/assets/syntaxes/02_Extra/Robot.sublime-syntaxU//home/martin/src/bat/assets/syntaxes/02_Extra/SCSS_Sass/Syntaxes/SCSS.sublime-syntaxU//home/martin/src/bat/assets/syntaxes/02_Extra/SCSS_Sass/Syntaxes/Sass.sublime-syntaxE//home/martin/src/bat/assets/syntaxes/02_Extra/SLS/SLS.sublime-syntaxA//home/martin/src/bat/assets/syntaxes/02_Extra/SML.sublime-syntaxB//home/martin/src/bat/assets/syntaxes/02_Extra/Slim.sublime-syntaxK//home/martin/src/bat/assets/syntaxes/02_Extra/Strace/strace.sublime-syntaxD//home/martin/src/bat/assets/syntaxes/02_Extra/Stylus.sublime-syntaxV//home/martin/src/bat/assets/syntaxes/02_Extra/SublimeEthereum/Solidity.sublime-syntaxS//home/martin/src/bat/assets/syntaxes/02_Extra/SublimeEthereum/Vyper.sublime-syntaxJ//home/martin/src/bat/assets/syntaxes/02_Extra/SublimeJQ/JQ.sublime-syntaxK//home/martin/src/bat/assets/syntaxes/02_Extra/Svelte/Svelte.sublime-syntaxC//home/martin/src/bat/assets/syntaxes/02_Extra/Swift.sublime-syntaxY//home/martin/src/bat/assets/syntaxes/02_Extra/SystemVerilog/SystemVerilog.sublime-syntaxR//home/martin/src/bat/assets/syntaxes/02_Extra/SystemVerilog/navbar.sublime-syntaxG//home/martin/src/bat/assets/syntaxes/02_Extra/TOML/TOML.sublime-syntaxX//home/martin/src/bat/assets/syntaxes/02_Extra/Terraform/JSON (Terraform).sublime-syntaxQ//home/martin/src/bat/assets/syntaxes/02_Extra/Terraform/Terraform.sublime-syntaxM//home/martin/src/bat/assets/syntaxes/02_Extra/TodoTxt/TodoTxt.sublime-syntaxH//home/martin/src/bat/assets/syntaxes/02_Extra/TypeScript.sublime-syntaxM//home/martin/src/bat/assets/syntaxes/02_Extra/TypsecriptReact.sublime-syntaxE//home/martin/src/bat/assets/syntaxes/02_Extra/Verilog.sublime-syntaxE//home/martin/src/bat/assets/syntaxes/02_Extra/VimHelp.sublime-syntaxG//home/martin/src/bat/assets/syntaxes/02_Extra/VimL/VimL.sublime-syntaxO//home/martin/src/bat/assets/syntaxes/02_Extra/Vue/Vue Component.sublime-syntaxN//home/martin/src/bat/assets/syntaxes/02_Extra/Zig/Syntaxes/Zig.sublime-syntaxX//home/martin/src/bat/assets/syntaxes/02_Extra/cmd-help/syntaxes/cmd-help.sublime-syntaxE//home/martin/src/bat/assets/syntaxes/02_Extra/gnuplot.sublime-syntaxi//home/martin/src/bat/assets/syntaxes/02_Extra/http-request-response/http-request-response.sublime-syntaxA//home/martin/src/bat/assets/syntaxes/02_Extra/log.sublime-syntaxO//home/martin/src/bat/assets/syntaxes/02_Extra/show-nonprintable.sublime-syntaxX//home/martin/src/bat/assets/syntaxes/02_Extra/ssh-config/Authorized Keys.sublime-syntaxT//home/martin/src/bat/assets/syntaxes/02_Extra/ssh-config/Known Hosts.sublime-syntaxT//home/martin/src/bat/assets/syntaxes/02_Extra/ssh-config/Private Key.sublime-syntaxS//home/martin/src/bat/assets/syntaxes/02_Extra/ssh-config/SSH Common.sublime-syntaxS//home/martin/src/bat/assets/syntaxes/02_Extra/ssh-config/SSH Config.sublime-syntaxS//home/martin/src/bat/assets/syntaxes/02_Extra/ssh-config/SSH Crypto.sublime-syntaxT//home/martin/src/bat/assets/syntaxes/02_Extra/ssh-config/SSHD Config.sublime-syntaxD//home/martin/src/bat/assets/syntaxes/02_Extra/syslog.sublime-syntaxM//home/martin/src/bat/assets/syntaxes/02_Extra/varlink/varlink.sublime-syntaxB//home/martin/src/bat/assets/syntaxes/02_Extra/wgsl.sublime-syntaxbroot-1.46.3/rustfmt.toml000064400000000000000000000001621046102023000134440ustar 00000000000000edition = "2021" version = "Two" imports_layout = "Vertical" fn_params_layout = "Vertical" newline_style = "Unix" broot-1.46.3/src/app/app.rs000064400000000000000000001071251046102023000135470ustar 00000000000000use { super::*, crate::{ browser::BrowserState, cli::TriBool, command::{ Command, Sequence, }, conf::Conf, display::*, errors::ProgramError, file_sum, git, kitty, launchable::Launchable, path::closest_dir, pattern::InputPattern, preview::PreviewState, skin::*, stage::Stage, syntactic::SyntaxTheme, task_sync::{ Dam, Either, }, terminal, verb::Internal, }, crokey::crossterm::{ cursor::MoveTo, event::Event, queue, }, std::{ io::Write, path::{Path, PathBuf}, str::FromStr, sync::{ Arc, Mutex, }, }, strict::NonEmptyVec, termimad::{ crossbeam::channel::{ unbounded, Receiver, Sender, }, EventSource, EventSourceOptions, }, }; /// The GUI pub struct App { /// dimensions of the screen screen: Screen, /// the panels of the application, at least one panels: NonEmptyVec, /// index of the currently focused panel active_panel_idx: usize, /// whether the app is in the (uncancellable) process of quitting quitting: bool, /// what must be done after having closed the TUI launch_at_end: Option, /// a count of all panels created created_panels_count: usize, /// the panel dedicated to preview, if any preview_panel: Option, stage_panel: Option, /// an optional copy of the root for the --server shared_root: Option>>, /// sender to the sequence channel tx_seqs: Sender, /// receiver to listen to the sequence channel rx_seqs: Receiver, /// counter incremented at every draw drawing_count: usize, } impl App { pub fn new(con: &AppContext) -> Result { let screen = Screen::new(con)?; let mut browser_state = Box::new(BrowserState::new( con.initial_root.clone(), con.initial_tree_options.clone(), screen, con, &Dam::unlimited(), )?); if let Some(path) = con.initial_file.as_ref() { browser_state.tree.try_select_path(path); } let panel = Panel::new( PanelId::from(0), browser_state, Areas::create(&mut Vec::new(), &con.layout_instructions, 0, screen, false), con, ); let (tx_seqs, rx_seqs) = unbounded::(); let mut app = App { screen, active_panel_idx: 0, panels: panel.into(), quitting: false, launch_at_end: None, created_panels_count: 1, preview_panel: None, stage_panel: None, shared_root: None, tx_seqs, rx_seqs, drawing_count: 0, }; if let Some(path) = con.initial_file.as_ref() { // open initial_file in preview let preview_state = Box::new(PreviewState::new( path.to_path_buf(), InputPattern::none(), None, con.initial_tree_options.clone(), con, )); if let Err(err) = app.new_panel( preview_state, PanelPurpose::Preview, HDir::Right, false, con, ) { warn!("could not open preview: {err}"); } else { // we focus the preview panel app.active_panel_idx = 1; } } Ok(app) } fn panel_ref_to_idx( &self, panel_ref: PanelReference, ) -> Option { match panel_ref { PanelReference::Active => Some(self.active_panel_idx), PanelReference::Leftest => Some(0), PanelReference::Rightest => Some(self.panels.len().get() - 1), PanelReference::Id(id) => self.panel_id_to_idx(id), PanelReference::Preview => self.preview_panel.and_then(|id| self.panel_id_to_idx(id)), } } /// return the current index of the panel with given id fn panel_id_to_idx( &self, id: PanelId, ) -> Option { self.panels.iter().position(|panel| panel.id == id) } fn state(&self) -> &dyn PanelState { self.panels[self.active_panel_idx].state() } fn mut_state(&mut self) -> &mut dyn PanelState { self.panels[self.active_panel_idx].mut_state() } fn panel(&self) -> &Panel { &self.panels[self.active_panel_idx] } fn mut_panel(&mut self) -> &mut Panel { unsafe { self.panels .as_mut_slice() .get_unchecked_mut(self.active_panel_idx) } } /// close the panel if it's not the last one /// /// Return true when the panel has been removed (ie it wasn't the last one) fn close_panel( &mut self, panel_idx: usize, con: &AppContext, ) -> bool { let active_panel_id = self.panels[self.active_panel_idx].id; if let Some(preview_id) = self.preview_panel { if self.panels.has_len(2) && self.panels[panel_idx].id != preview_id { // we don't want to stay with just the preview return false; } } if let Some(stage_id) = self.stage_panel { if self.panels.has_len(2) && self.panels[panel_idx].id != stage_id { // we don't want to stay with just the stage return false; } } if let Ok(removed_panel) = self.panels.remove(panel_idx) { if self.preview_panel == Some(removed_panel.id) { self.preview_panel = None; } if self.stage_panel == Some(removed_panel.id) { self.stage_panel = None; } Areas::resize_all( self.panels.as_mut_slice(), &con.layout_instructions, self.screen, self.preview_panel.is_some(), ); self.active_panel_idx = self .panels .iter() .position(|p| p.id == active_panel_id) .unwrap_or(self.panels.len().get() - 1); true } else { false // there's no other panel to go to } } /// remove the top state of the current panel /// /// Close the panel too if that was its only state. /// Close nothing and return false if there's not /// at least two states in the app. fn remove_state(&mut self, con: &AppContext) -> bool { self.panels[self.active_panel_idx].remove_state() || self.close_panel(self.active_panel_idx, con) } /// redraw the whole screen. All drawing /// are supposed to happen here, and only here. fn display_panels( &mut self, w: &mut W, skin: &AppSkin, app_state: &AppState, con: &AppContext, ) -> Result<(), ProgramError> { self.drawing_count += 1; let mut cursor_pos = None; for (idx, panel) in self.panels.as_mut_slice().iter_mut().enumerate() { let active = idx == self.active_panel_idx; let panel_skin = if active { &skin.focused } else { &skin.unfocused }; let disc = DisplayContext { count: self.drawing_count, active, screen: self.screen, panel_skin, state_area: panel.areas.state.clone(), app_state, con, }; if let Some(pos) = time!("display panel", panel.display(w, &disc)?,) { cursor_pos = Some(pos) } } // after drawing all the panels, move cursor to the end of the active panel input, // so that input methods can popup at correct position. if let Some((left, top)) = cursor_pos { queue!(w, MoveTo(left, top))?; } kitty::manager() .lock() .unwrap() .erase_images_before(w, self.drawing_count)?; w.flush()?; Ok(()) } /// if there are exactly two non preview panels, return the selection /// in the non focused panel fn get_other_panel_path(&self) -> Option { let len = self.panels.len().get(); if len == 3 { if let Some(preview_id) = self.preview_panel { for (idx, panel) in self.panels.iter().enumerate() { if self.active_panel_idx != idx && panel.id != preview_id { return panel.state().selected_path().map(Path::to_path_buf); } } } None } else if self.panels.len().get() == 2 && self.preview_panel.is_none() { let non_focused_panel_idx = if self.active_panel_idx == 0 { 1 } else { 0 }; self.panels[non_focused_panel_idx] .state() .selected_path() .map(Path::to_path_buf) } else { None } } /// apply a command. Change the states but don't redraw on screen. fn apply_command( &mut self, w: &mut W, cmd: Command, panel_skin: &PanelSkin, app_state: &mut AppState, con: &mut AppContext, ) -> Result<(), ProgramError> { use CmdResult::*; let mut error: Option = None; let mut new_active_panel_idx = None; let is_input_invocation = cmd.is_verb_invocated_from_input(); let app_cmd_context = AppCmdContext { panel_skin, preview_panel: self.preview_panel, stage_panel: self.stage_panel, screen: self.screen, // it can't change in this function con, }; let cmd_result = self .mut_panel() .apply_command(w, &cmd, app_state, &app_cmd_context)?; info!("cmd_result: {:?}", &cmd_result); match cmd_result { ApplyOnPanel { id } => { if let Some(idx) = self.panel_id_to_idx(id) { if let DisplayError(txt) = self.panels[idx].apply_command(w, &cmd, app_state, &app_cmd_context)? { // we should probably handle other results // which implies the possibility of a recursion error = Some(txt); } else if is_input_invocation { self.mut_panel().clear_input(); } } else { warn!("no panel found for ApplyOnPanel"); } } ClosePanel { validate_purpose, panel_ref, clear_cache, } => { if is_input_invocation { self.mut_panel().clear_input_invocation(con); } let close_idx = self.panel_ref_to_idx(panel_ref) .unwrap_or_else(|| // when there's a preview panel, we close it rather than the app if self.panels.len().get()==2 && self.preview_panel.is_some() { 1 } else { self.active_panel_idx } ); let mut new_arg = None; if validate_purpose { let purpose = &self.panels[close_idx].purpose; if let PanelPurpose::ArgEdition { .. } = purpose { new_arg = self.panels[close_idx] .state() .selected_path() .map(|p| p.to_string_lossy().to_string()); } } if clear_cache { clear_caches(); } if self.close_panel(close_idx, con) { let screen = self.screen; self.mut_state().refresh(screen, con); if let Some(new_arg) = new_arg { self.mut_panel().set_input_arg(new_arg); let new_input = self.panel().get_input_content(); let cmd = Command::from_raw(new_input, false); let app_cmd_context = AppCmdContext { panel_skin, preview_panel: self.preview_panel, stage_panel: self.stage_panel, screen, con, }; self.mut_panel() .apply_command(w, &cmd, app_state, &app_cmd_context)?; } } else { self.quitting = true; } } ChangeLayout(instruction) => { con.layout_instructions.push(instruction); Areas::resize_all( self.panels.as_mut_slice(), &con.layout_instructions, self.screen, self.preview_panel.is_some(), ); } DisplayError(txt) => { error = Some(txt); } ExecuteSequence { sequence } => { self.tx_seqs.send(sequence).unwrap(); } HandleInApp(internal) => { debug!("handling internal {internal:?} at app level"); match internal { Internal::escape => { let mode = self.panel().state().get_mode(); let cmd = self.mut_panel().input.escape(con, mode); debug!("cmd on escape: {cmd:?}"); self.apply_command(w, cmd, panel_skin, app_state, con)?; } Internal::focus_staging_area_no_open => { new_active_panel_idx = self .panels .iter() .position(|p| p.state().get_type() == PanelStateType::Stage); } Internal::panel_left_no_open => { // we're here because the state wants us to either move to the panel // to the left, or close the rightest one new_active_panel_idx = if self.active_panel_idx == 0 { self.close_panel(self.panels.len().get() - 1, con); None } else { Some(self.active_panel_idx - 1) }; } Internal::panel_right_no_open => { // we either move to the right or close the leftest panel new_active_panel_idx = if self.active_panel_idx + 1 == self.panels.len().get() { self.close_panel(0, con); None } else { Some(self.active_panel_idx + 1) }; } Internal::search_again => { if let Some(raw_pattern) = &self.panel().last_raw_pattern { let sequence = Sequence::new_single(raw_pattern.clone()); self.tx_seqs.send(sequence).unwrap(); } } Internal::set_syntax_theme => { let arg = cmd.as_verb_invocation().and_then(|vi| vi.args.as_ref()); match arg { Some(arg) => match SyntaxTheme::from_str(arg) { Ok(theme) => { con.syntax_theme = Some(theme); self.update_preview(con, true); } Err(e) => { error = Some(e.to_string()); } }, None => { error = Some("no theme provided".to_string()); } } } Internal::toggle_second_tree => { let panels_count = self.panels.len().get(); let trees_count = self .panels .iter() .filter(|p| p.state().get_type() == PanelStateType::Tree) .count(); if trees_count < 2 { // we open a tree, closing a (non tree) panel if necessary if panels_count >= con.max_panels_count { for i in (0..panels_count).rev() { if self.panels[i].state().get_type() != PanelStateType::Tree { self.close_panel(i, con); break; } } } if let Some(selected_path) = self.state().selected_path() { let dir = closest_dir(selected_path); if let Ok(new_state) = BrowserState::new( dir, self.state().tree_options().without_pattern(), self.screen, con, &Dam::unlimited(), ) { if let Err(s) = self.new_panel( Box::new(new_state), PanelPurpose::None, HDir::Right, is_input_invocation, con, ) { error = Some(s); } } } } else { // we close the rightest inactive tree for i in (0..panels_count).rev() { if self.panels[i].state().get_type() == PanelStateType::Tree { if i == self.active_panel_idx { continue; } self.close_panel(i, con); break; } } } } _ => { let cmd = self.mut_panel().input.on_internal(internal); if cmd.is_none() { warn!("unhandled propagated internal. internal={internal:?} cmd={cmd:?}"); } else { self.apply_command(w, cmd, panel_skin, app_state, con)?; } } } } Keep => { if is_input_invocation { self.mut_panel().clear_input_invocation(con); } } Message(md) => { if is_input_invocation { self.mut_panel().clear_input_invocation(con); } self.mut_panel().set_message(md); } Launch(launchable) => { self.launch_at_end = Some(*launchable); self.quitting = true; } NewPanel { state, purpose, direction, } => { if let Err(s) = self.new_panel(state, purpose, direction, is_input_invocation, con) { error = Some(s); } } NewState { state, message } => { self.mut_panel().clear_input(); self.mut_panel().push_state(state); if let Some(md) = message { self.mut_panel().set_message(md); } else { self.mut_panel() .refresh_input_status(app_state, &app_cmd_context); } } PopState => { if is_input_invocation { self.mut_panel().clear_input(); } if self.remove_state(con) { self.mut_state().refresh(app_cmd_context.screen, con); self.mut_panel() .refresh_input_status(app_state, &app_cmd_context); } else if con.quit_on_last_cancel { self.quitting = true; } } PopStateAndReapply => { if is_input_invocation { self.mut_panel().clear_input(); } if self.remove_state(con) { let app_cmd_context = AppCmdContext { panel_skin, preview_panel: self.preview_panel, stage_panel: self.stage_panel, screen: self.screen, con, }; self.mut_panel() .apply_command(w, &cmd, app_state, &app_cmd_context)?; } else if con.quit_on_last_cancel { self.quitting = true; } } Quit => { self.quitting = true; } RefreshState { clear_cache } => { info!("refreshing, clearing cache={clear_cache}"); if is_input_invocation { self.mut_panel().clear_input_invocation(con); } if clear_cache { clear_caches(); } app_state.stage.refresh(); for i in 0..self.panels.len().get() { self.panels[i].mut_state().refresh(self.screen, con); } } } if let Some(text) = error { self.mut_panel().set_error(text); } if let Some(idx) = new_active_panel_idx { if is_input_invocation { self.mut_panel().clear_input(); } self.active_panel_idx = idx; let app_cmd_context = AppCmdContext { panel_skin, preview_panel: self.preview_panel, stage_panel: self.stage_panel, screen: self.screen, con, }; self.mut_panel() .refresh_input_status(app_state, &app_cmd_context); } app_state.other_panel_path = self.get_other_panel_path(); if let Some(path) = self.state().tree_root() { app_state.root = path.to_path_buf(); terminal::update_title(w, app_state, con); if con.update_work_dir { if let Err(e) = std::env::set_current_dir(&app_state.root) { warn!("Failed to set current dir: {e}"); } } if let Some(shared_root) = &mut self.shared_root { if let Ok(mut root) = shared_root.lock() { root.clone_from(&app_state.root); } } } self.update_preview(con, false); Ok(()) } /// update the state of the preview, if there's some fn update_preview( &mut self, con: &AppContext, refresh: bool, ) { let preview_idx = self.preview_panel.and_then(|id| self.panel_id_to_idx(id)); if let Some(preview_idx) = preview_idx { if let Some(path) = self.state().selected_path() { let old_path = self.panels[preview_idx].state().selected_path(); if refresh || Some(path) != old_path { let path = path.to_path_buf(); self.panels[preview_idx] .mut_state() .set_selected_path(path, con); } } } } /// get the index of the panel at x fn clicked_panel_index( &self, x: u16, _y: u16, ) -> usize { let len = self.panels.len().get(); (len * x as usize) / (self.screen.width as usize + 1) } /// handle CmdResult::NewPanel fn new_panel( &mut self, state: Box, purpose: PanelPurpose, direction: HDir, is_input_invocation: bool, // if true we clean the input con: &AppContext, ) -> Result<(), String> { match state.get_type() { PanelStateType::Preview if self.preview_panel.is_some() => { return Err("There can be only one preview panel".to_owned()); // todo replace instead ? } PanelStateType::Stage if self.stage_panel.is_some() => { return Err("There can be only one stage panel".to_owned()); // todo replace instead ? } _ => {} } if is_input_invocation { self.mut_panel().clear_input_invocation(con); } let insertion_idx = if purpose.is_preview() { self.panels.len().get() } else if direction == HDir::Right { self.active_panel_idx + 1 } else { self.active_panel_idx }; let with_preview = purpose.is_preview() || self.preview_panel.is_some(); let areas = Areas::create( self.panels.as_mut_slice(), &con.layout_instructions, insertion_idx, self.screen, with_preview, ); let panel_id = self.created_panels_count.into(); match state.get_type() { PanelStateType::Preview => { self.preview_panel = Some(panel_id); } PanelStateType::Stage => { self.stage_panel = Some(panel_id); } _ => { self.active_panel_idx = insertion_idx; } } let mut panel = Panel::new(panel_id, state, areas, con); panel.purpose = purpose; self.created_panels_count += 1; self.panels.insert(insertion_idx, panel); Ok(()) } /// do the pending tasks, if any, and refresh the screen accordingly fn do_pending_tasks( &mut self, w: &mut W, skin: &AppSkin, dam: &mut Dam, app_state: &mut AppState, con: &AppContext, ) -> Result<(), ProgramError> { while self.has_pending_task() && !dam.has_event() { let error = self.do_pending_task(app_state, con, dam).err(); self.update_preview(con, false); // the selection may have changed if let Some(error) = &error { self.mut_panel().set_error(error.to_string()); } else { let panel_skin = &skin.focused; let app_cmd_context = AppCmdContext { panel_skin, preview_panel: self.preview_panel, stage_panel: self.stage_panel, screen: self.screen, // it can't change in this function con, }; self.mut_panel() .refresh_input_status(app_state, &app_cmd_context); } self.display_panels(w, skin, app_state, con)?; if error.is_some() { return Ok(()); // breaking pending tasks chain on first error/interruption } } Ok(()) } /// Do the next pending task fn do_pending_task( &mut self, app_state: &mut AppState, con: &AppContext, dam: &mut Dam, ) -> Result<(), ProgramError> { let screen = self.screen; // we start with the focused panel if self.panel().has_pending_task() { return self .mut_panel() .do_pending_task(app_state, screen, con, dam); } // then the other ones for idx in 0..self.panels.len().get() { if idx != self.active_panel_idx { let panel = &mut self.panels[idx]; if panel.has_pending_task() { return panel.do_pending_task(app_state, screen, con, dam); } } } warn!("unexpected lack of pending task"); Ok(()) } fn has_pending_task(&mut self) -> bool { self.panels.iter().any(Panel::has_pending_task) } /// This is the main loop of the application pub fn run( mut self, w: &mut W, con: &mut AppContext, conf: &Conf, ) -> Result, ProgramError> { #[cfg(feature = "clipboard")] { // different systems have different clipboard capabilities // and it may be useful to know which one we have debug!("Clipboard backend: {:?}", terminal_clipboard::get_type()); } // we listen for events in a separate thread so that we can go on listening // when a long search is running, and interrupt it if needed w.flush()?; let combine_keys = conf.enable_kitty_keyboard.unwrap_or(false) && con.is_tty; let event_source = EventSource::with_options( EventSourceOptions { combine_keys, ..Default::default() } )?; con.keyboard_enhanced = event_source.supports_multi_key_combinations(); info!("event source is combining: {}", event_source.supports_multi_key_combinations()); let rx_events = event_source.receiver(); let mut dam = Dam::from(rx_events); let skin = AppSkin::new(conf, con.launch_args.color == TriBool::No); let mut app_state = AppState { stage: Stage::default(), root: con.initial_root.clone(), other_panel_path: None, }; terminal::update_title(w, &app_state, con); self.screen.clear_bottom_right_char(w, &skin.focused)?; #[cfg(windows)] if con.cmd().is_some() { // Powershell sends to broot a resize event after it was launched // which interrupts its task queue. An easy fix is to wait for a // few ms for the terminal to be stabilized. // It's possible some other terminals, even not on Windows, might // need the same trick in the future let delay = std::time::Duration::from_millis(10); std::thread::sleep(delay); let dropped_events = dam.clear(); debug!("Dropped {dropped_events} events"); event_source.unblock(self.quitting); } if let Some(raw_sequence) = &con.cmd() { self.tx_seqs .send(Sequence::new_local(raw_sequence.to_string())) .unwrap(); } #[cfg(unix)] let _server = con .launch_args .listen .as_ref() .map(|server_name| { let shared_root = Arc::new(Mutex::new(app_state.root.clone())); let server = crate::net::Server::new( server_name, self.tx_seqs.clone(), Arc::clone(&shared_root), ); self.shared_root = Some(shared_root); server }) .transpose()?; loop { if !self.quitting { self.display_panels(w, &skin, &app_state, con)?; time!( Debug, "pending_tasks", self.do_pending_tasks(w, &skin, &mut dam, &mut app_state, con)?, ); } #[allow(unused_mut)] match dam.next(&self.rx_seqs) { Either::First(Some(event)) => { //info!("event: {:?}", &event); if let Some(key_combination) = event.key_combination { debug!("key combination: {}", key_combination); } let mut handled = false; // app level handling if let Some((x, y)) = event.as_click() { if self.clicked_panel_index(x, y) != self.active_panel_idx { // panel activation click self.active_panel_idx = self.clicked_panel_index(x, y); handled = true; } } else if let Event::Resize(mut width, mut height) = event.event { self.screen.set_terminal_size(width, height, con); Areas::resize_all( self.panels.as_mut_slice(), &con.layout_instructions, self.screen, self.preview_panel.is_some(), ); for panel in &mut self.panels { panel.mut_state().refresh(self.screen, con); } handled = true; } // event handled by the panel if !handled { let cmd = self.mut_panel().add_event(w, event, &app_state, con)?; debug!("command after add_event: {:?}", &cmd); self.apply_command(w, cmd, &skin.focused, &mut app_state, con)?; } event_source.unblock(self.quitting); } Either::First(None) => { // this is how we quit the application, // when the input thread is properly closed break; } Either::Second(Some(raw_sequence)) => { debug!("got command sequence: {:?}", &raw_sequence); for (input, arg_cmd) in raw_sequence.parse(con)? { if !matches!(&arg_cmd, Command::Internal{..}) { self.mut_panel().set_input_content(&input); } self.apply_command(w, arg_cmd, &skin.focused, &mut app_state, con)?; if self.quitting { return Ok(self.launch_at_end.take()); } self.display_panels(w, &skin, &app_state, con)?; time!( "sequence pending tasks", self.do_pending_tasks(w, &skin, &mut dam, &mut app_state, con)?, ); } } Either::Second(None) => { warn!("I didn't expect a None to occur here"); } } } terminal::reset_title(w, con); Ok(self.launch_at_end.take()) } } /// clear the file sizes and git stats cache. /// /// This should be done on Refresh actions and after any external command. fn clear_caches() { file_sum::clear_cache(); git::clear_status_computer_cache(); #[cfg(unix)] crate::filesystems::clear_cache(); } broot-1.46.3/src/app/app_context.rs000064400000000000000000000277051046102023000153200ustar 00000000000000use { super::*, crate::{ app::Mode, cli::{Args, TriBool}, conf::*, content_search, display::LayoutInstructions, errors::*, file_sum, icon::*, kitty::TransmissionMedium, pattern::SearchModeMap, path::SpecialPaths, preview::PreviewTransformers, skin::ExtColorMap, syntactic::SyntaxTheme, tree::TreeOptions, verb::*, }, crokey::crossterm::tty::IsTty, std::{ convert::{TryFrom, TryInto}, io, num::NonZeroUsize, path::{Path, PathBuf}, }, }; /// The container that can be passed around to provide the configuration things /// for the whole life of the App pub struct AppContext { /// Whether the application is running in a normal TTY context pub is_tty: bool, /// The initial tree root pub initial_root: PathBuf, /// The initial file to select and preview pub initial_file: Option, /// Initial tree options pub initial_tree_options: TreeOptions, /// where's the config file we're using /// This vec can't be empty pub config_paths: Vec, /// all the arguments specified at launch pub launch_args: Args, /// the "launch arguments" found in the default_flags /// of the config file(s) pub config_default_args: Option, /// the verbs in use (builtins and configured ones) pub verb_store: VerbStore, /// the paths for which there's a special behavior to follow (comes from conf) pub special_paths: SpecialPaths, /// the map between search prefixes and the search mode to apply pub search_modes: SearchModeMap, /// whether to show a triangle left to selected lines pub show_selection_mark: bool, /// mapping from file extension to colors (comes from conf) pub ext_colors: ExtColorMap, /// the syntect theme to use for text files previewing pub syntax_theme: Option, /// precomputed status to display in standard cases /// (ie when no verb is involved) pub standard_status: StandardStatus, /// whether we can use 24 bits colors for previewed images pub true_colors: bool, /// map extensions to icons, icon set chosen based on config /// Send, Sync safely because once created, everything is immutable pub icons: Option>, /// modal (aka "vim) mode enabled pub modal: bool, /// the initial mode (only relevant when modal is true) pub initial_mode: Mode, /// Whether to support mouse interactions pub capture_mouse: bool, /// max number of panels (including preview) that can be /// open. Guaranteed to be at least 2. pub max_panels_count: usize, /// whether to quit broot when the user hits "escape" /// and there's nothing to cancel pub quit_on_last_cancel: bool, /// number of threads used by file_sum (count, size, date) /// computation pub file_sum_threads_count: usize, /// number of files which may be staged in one staging operation pub max_staged_count: usize, /// max file size when searching file content pub content_search_max_file_size: usize, /// the optional pattern used to change the terminal's title /// (if none, the title isn't modified) pub terminal_title_pattern: Option, /// whether to reset the terminal's title on exit pub reset_terminal_title_on_exit: bool, /// whether to sync broot's work dir with the current panel's root pub update_work_dir: bool, /// Whether Kitty keyboard enhancement flags are pushed, so that /// we know whether we need to temporarily disable them during /// the execution of a terminal program. /// This is determined by app::run on launching the event source. pub keyboard_enhanced: bool, pub kitty_graphics_transmission: TransmissionMedium, pub kept_kitty_temp_files: NonZeroUsize, /// Number of lines to display after a match in the preview pub lines_after_match_in_preview: usize, /// Number of lines to display before a match in the preview pub lines_before_match_in_preview: usize, /// The set of transformers called before previewing a file pub preview_transformers: PreviewTransformers, /// layout modifiers, like divider moves pub layout_instructions: LayoutInstructions, } impl AppContext { pub fn from( launch_args: Args, verb_store: VerbStore, config: &Conf, ) -> Result { let is_tty = std::io::stdout().is_tty(); let config_default_args = config .default_flags .as_ref() .map(|flags| parse_default_flags(flags)) .transpose()?; let config_paths = config.files.clone(); let standard_status = StandardStatus::new(&verb_store); let true_colors = if let Some(value) = config.true_colors { value } else { are_true_colors_available() }; let icons = config.icon_theme.as_ref() .and_then(|itn| icon_plugin(itn)); let mut special_paths: SpecialPaths = (&config.special_paths).try_into()?; special_paths.add_defaults(); let search_modes = config .search_modes .as_ref() .map(TryInto::try_into) .transpose()? .unwrap_or_default(); let ext_colors = ExtColorMap::try_from(&config.ext_colors) .map_err(ConfError::from)?; let file_sum_threads_count = config.file_sum_threads_count .unwrap_or(file_sum::DEFAULT_THREAD_COUNT); if !(1..=50).contains(&file_sum_threads_count) { return Err(ConfError::InvalidThreadsCount{ count: file_sum_threads_count }.into()); } let max_panels_count = config.max_panels_count .unwrap_or(2) .clamp(2, 100); let capture_mouse = match (config.capture_mouse, config.disable_mouse_capture) { (Some(b), _) => b, // the new "capture_mouse" argument takes precedence (_, Some(b)) => !b, _ => true, }; let max_staged_count = config.max_staged_count .unwrap_or(10_000) .clamp(10, 100_000); let (initial_root, initial_file) = initial_root_file(&launch_args)?; // tree options are built from the default_flags // found in the config file(s) (if any) then overridden // by the cli args (order is important) let mut initial_tree_options = TreeOptions::default(); initial_tree_options.apply_config(config)?; if let Some(args) = &config_default_args { initial_tree_options.apply_launch_args(args); } initial_tree_options.apply_launch_args(&launch_args); if launch_args.color == TriBool::No { initial_tree_options.show_selection_mark = true; } let content_search_max_file_size = config.content_search_max_file_size .map(|u64value| usize::try_from(u64value).unwrap_or(usize::MAX)) .unwrap_or(content_search::DEFAULT_MAX_FILE_SIZE); let terminal_title_pattern = config.terminal_title.clone(); let reset_terminal_title_on_exit = config.reset_terminal_title_on_exit.unwrap_or(false); let preview_transformers = PreviewTransformers::new(&config.preview_transformers)?; let layout_instructions = config.layout_instructions.clone().unwrap_or_default(); let kept_kitty_temp_files = config.kept_kitty_temp_files.unwrap_or( std::num::NonZeroUsize::new(500).unwrap(), ); Ok(Self { is_tty, initial_root, initial_file, initial_tree_options, config_paths, launch_args, config_default_args, verb_store, special_paths, search_modes, show_selection_mark: config.show_selection_mark.unwrap_or(false), ext_colors, syntax_theme: config.syntax_theme, standard_status, true_colors, icons, modal: config.modal.unwrap_or(false), initial_mode: config.initial_mode.unwrap_or(Mode::Command), capture_mouse, max_panels_count, quit_on_last_cancel: config.quit_on_last_cancel.unwrap_or(false), file_sum_threads_count, max_staged_count, content_search_max_file_size, terminal_title_pattern, reset_terminal_title_on_exit, update_work_dir: config.update_work_dir.unwrap_or(true), keyboard_enhanced: false, kitty_graphics_transmission: config.kitty_graphics_transmission .unwrap_or_default(), kept_kitty_temp_files, lines_after_match_in_preview: config.lines_after_match_in_preview.unwrap_or(0), lines_before_match_in_preview: config.lines_before_match_in_preview.unwrap_or(0), preview_transformers, layout_instructions, }) } /// Return the --cmd argument, coming from the launch arguments (preferred) /// or from the default_flags parameter of a config file pub fn cmd(&self) -> Option<&str> { self.launch_args.cmd.as_ref().or( self.config_default_args.as_ref().and_then(|args| args.cmd.as_ref()) ).map(String::as_str) } pub fn initial_mode(&self) -> Mode { if self.modal { self.initial_mode } else { Mode::Input } } } /// An unsafe implementation of Default, for tests only #[cfg(test)] impl Default for AppContext { fn default() -> Self { let mut config = Conf::default(); let verb_store = VerbStore::new(&mut config).unwrap(); let launch_args = parse_default_flags("").unwrap(); Self::from(launch_args, verb_store, &config).unwrap() } } /// try to determine whether the terminal supports true /// colors. This doesn't work well, hence the use of an /// optional config setting. /// Based on https://gist.github.com/XVilka/8346728#true-color-detection fn are_true_colors_available() -> bool { if let Ok(colorterm) = std::env::var("COLORTERM") { debug!("COLORTERM env variable = {:?}", colorterm); if colorterm.contains("truecolor") || colorterm.contains("24bit") { debug!("true colors are available"); true } else { false } } else { // this is debatable... I've found some terminals with COLORTERM // unset but supporting true colors. As it's easy to determine // that true colors aren't supported when looking at previewed // images I prefer this value true } } /// Determine the initial root folder to show, and the optional /// initial file to open in preview fn initial_root_file(cli_args: &Args) -> Result<(PathBuf, Option), ProgramError> { let mut file = None; let mut root = match cli_args.root.as_ref() { Some(path) => canonicalize_root(path)?, None => std::env::current_dir()?, }; if !root.exists() { return Err(TreeBuildError::FileNotFound { path: format!("{:?}", &root), }.into()); } if !root.is_dir() { // we try to open the parent directory if the passed file isn't one if let Some(parent) = root.parent() { file = Some(root.clone()); info!("Passed path isn't a directory => opening parent instead"); root = parent.to_path_buf(); } else { // this is a weird filesystem, let's give up return Err(TreeBuildError::NotADirectory { path: format!("{:?}", &root), }.into()); } } Ok((root, file)) } #[cfg(not(windows))] fn canonicalize_root(root: &Path) -> io::Result { root.canonicalize() } #[cfg(windows)] fn canonicalize_root(root: &Path) -> io::Result { Ok(if root.is_relative() { std::env::current_dir()?.join(root) } else { root.to_path_buf() }) } broot-1.46.3/src/app/app_state.rs000064400000000000000000000007011046102023000147370ustar 00000000000000use { crate::{ stage::Stage, }, std::path::PathBuf, }; /// global mutable state #[derive(Debug)] pub struct AppState { pub stage: Stage, /// the current root, updated when a panel with this concept /// becomes active or changes its root pub root: PathBuf, /// the selected path in another panel than the currently /// active one, if any pub other_panel_path: Option, } impl AppState { } broot-1.46.3/src/app/cmd_context.rs000064400000000000000000000016171046102023000152750ustar 00000000000000use { super::*, crate::{ command::*, display::{Areas, Screen}, skin::PanelSkin, }, }; /// short lived wrapping of a few things which are needed for the handling /// of a command in a panel and won't be modified during the operation. pub struct CmdContext<'c> { pub cmd: &'c Command, pub app: &'c AppCmdContext<'c>, pub panel: PanelCmdContext<'c>, } /// the part of the immutable command execution context which comes from the app pub struct AppCmdContext<'c> { pub panel_skin: &'c PanelSkin, pub preview_panel: Option, // id of the app's preview panel pub stage_panel: Option, // id of the app's preview panel pub screen: Screen, pub con: &'c AppContext, } /// the part of the command execution context which comes from the panel pub struct PanelCmdContext<'c> { pub areas: &'c Areas, pub purpose: PanelPurpose, } broot-1.46.3/src/app/cmd_result.rs000064400000000000000000000076701046102023000151340ustar 00000000000000use { super::*, crate::{ browser::BrowserState, command::Sequence, display::LayoutInstruction, errors::TreeBuildError, launchable::Launchable, verb::Internal, }, std::fmt, }; /// Either left or right #[derive(Debug, Clone, Copy, PartialEq)] pub enum HDir { Left, Right, } /// the symbolic reference to the panel to close #[derive(Debug, Clone, Copy, PartialEq)] pub enum PanelReference { Active, Leftest, Rightest, Id(PanelId), Preview, } /// Result of applying a command to a state pub enum CmdResult { ApplyOnPanel { id: PanelId, }, ClosePanel { validate_purpose: bool, panel_ref: PanelReference, clear_cache: bool, }, ChangeLayout(LayoutInstruction), DisplayError(String), ExecuteSequence { sequence: Sequence, }, HandleInApp(Internal), // command must be handled at the app level Keep, Message(String), Launch(Box), NewPanel { state: Box, purpose: PanelPurpose, direction: HDir, }, NewState { state: Box, message: Option<&'static str>, // explaining why there's a new state }, PopStateAndReapply, // the state asks the command be executed on a previous state PopState, Quit, RefreshState { clear_cache: bool, }, } impl CmdResult { pub fn verb_not_found(text: &str) -> CmdResult { CmdResult::DisplayError(format!("verb not found: {:?}", &text)) } pub fn from_optional_browser_state( os: Result, message: Option<&'static str>, in_new_panel: bool, ) -> CmdResult { match os { Ok(os) => { if in_new_panel { CmdResult::NewPanel { // TODO keep the message ? state: Box::new(os), purpose: PanelPurpose::None, direction: HDir::Right, } } else { CmdResult::NewState { state: Box::new(os), message, } } } Err(TreeBuildError::Interrupted) => CmdResult::Keep, Err(e) => CmdResult::error(e.to_string()), } } pub fn new_state(state: Box) -> Self { Self::NewState { state, message: None } } pub fn error>(message: S) -> Self { Self::DisplayError(message.into()) } } impl From for CmdResult { fn from(launchable: Launchable) -> Self { CmdResult::Launch(Box::new(launchable)) } } impl fmt::Debug for CmdResult { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( f, "{}", match self { CmdResult::ApplyOnPanel { .. } => "ApplyOnPanel", CmdResult::ChangeLayout(_) => "ChangeLayout", CmdResult::ClosePanel { validate_purpose: false, .. } => "CancelPanel", CmdResult::ClosePanel { validate_purpose: true, .. } => "OkPanel", CmdResult::DisplayError(_) => "DisplayError", CmdResult::ExecuteSequence{ .. } => "ExecuteSequence", CmdResult::Keep => "Keep", CmdResult::Message { .. } => "Message", CmdResult::Launch(_) => "Launch", CmdResult::NewState { .. } => "NewState", CmdResult::NewPanel { .. } => "NewPanel", CmdResult::PopStateAndReapply => "PopStateAndReapply", CmdResult::PopState => "PopState", CmdResult::HandleInApp(_) => "HandleInApp", CmdResult::Quit => "Quit", CmdResult::RefreshState { .. } => "RefreshState", } ) } } broot-1.46.3/src/app/display_context.rs000064400000000000000000000006501046102023000161730ustar 00000000000000use { super::*, crate::{ display::Screen, skin::PanelSkin, }, termimad::Area, }; /// short lived wrapping of a few things which are needed for displaying /// panels pub struct DisplayContext<'c> { pub count: usize, pub active: bool, pub screen: Screen, pub state_area: Area, pub panel_skin: &'c PanelSkin, pub app_state: &'c AppState, pub con: &'c AppContext, } broot-1.46.3/src/app/mod.rs000064400000000000000000000011061046102023000135360ustar 00000000000000mod app; mod app_context; mod app_state; mod cmd_context; mod cmd_result; mod display_context; mod mode; mod panel; mod panel_id; mod panel_purpose; mod panel_state; mod selection; mod standard_status; mod state_type; mod status; pub use { app::App, app_context::AppContext, app_state::*, cmd_context::*, cmd_result::*, display_context::*, mode::*, panel::Panel, panel_id::PanelId, panel_purpose::PanelPurpose, panel_state::*, selection::*, standard_status::StandardStatus, state_type::PanelStateType, status::Status, }; broot-1.46.3/src/app/mode.rs000064400000000000000000000004311046102023000137030ustar 00000000000000use { serde::Deserialize, }; /// modes are used when the application is configured to /// be "modal". If not, the only mode is the `Input` mode. #[derive(Debug, Clone, Copy, PartialEq, Deserialize)] #[serde(rename_all = "lowercase")] pub enum Mode { Input, Command, } broot-1.46.3/src/app/panel.rs000064400000000000000000000224341046102023000140650ustar 00000000000000use { super::*, crate::{ command::*, display::{ status_line, Areas, Screen, W, WIDE_STATUS, flags_display, }, errors::ProgramError, keys::KEY_FORMAT, skin::PanelSkin, task_sync::Dam, verb::*, }, termimad::{ minimad::{Alignment, Composite}, TimedEvent, }, }; /// A column on screen containing a stack of states, the top /// one being visible pub struct Panel { pub id: PanelId, states: Vec>, // stack: the last one is current pub areas: Areas, status: Status, pub purpose: PanelPurpose, pub input: PanelInput, pub last_raw_pattern: Option, } impl Panel { pub fn new( id: PanelId, state: Box, areas: Areas, con: &AppContext, ) -> Self { let mut input = PanelInput::new(areas.input.clone()); input.set_content(&state.get_starting_input()); let status = state.no_verb_status(false, con, areas.status.width as usize); Self { id, states: vec![state], areas, status, purpose: PanelPurpose::None, input, last_raw_pattern: None, } } pub fn set_error(&mut self, text: String) { self.status = Status::from_error(text); } pub fn set_message>(&mut self, md: S) { self.status = Status::from_message(md.into()); } /// apply a command on the current state, with no /// effect on screen pub fn apply_command<'c>( &mut self, w: &'c mut W, cmd: &'c Command, app_state: &mut AppState, app_cmd_context: &'c AppCmdContext<'c>, ) -> Result { if let Command::PatternEdit { raw, .. } = cmd { self.last_raw_pattern = Some(raw.clone()); } let state_idx = self.states.len() - 1; let cc = CmdContext { cmd, app: app_cmd_context, panel: PanelCmdContext { areas: &self.areas, purpose: self.purpose, }, }; let result = self.states[state_idx].on_command(w, app_state, &cc); let has_previous_state = self.states.len() > 1; self.status = self.state().get_status(app_state, &cc, has_previous_state, self.areas.status.width as usize); result } /// called on focusing the panel and before the display, /// this updates the status from the command read in the input pub fn refresh_input_status<'c>( &mut self, app_state: &AppState, app_cmd_context: &'c AppCmdContext<'c>, ) { let cmd = Command::from_raw(self.input.get_content(), false); let cc = CmdContext { cmd: &cmd, app: app_cmd_context, panel: PanelCmdContext { areas: &self.areas, purpose: self.purpose, }, }; let has_previous_state = self.states.len() > 1; self.status = self.state().get_status(app_state, &cc, has_previous_state, self.areas.status.width as usize); } /// do the next pending task stopping as soon as there's an event /// in the dam pub fn do_pending_task( &mut self, app_state: &mut AppState, screen: Screen, con: &AppContext, dam: &mut Dam, ) -> Result<(), ProgramError> { self.mut_state().do_pending_task(app_state, screen, con, dam) } pub fn has_pending_task(&self) -> bool { self.state().get_pending_task().is_some() } /// return a new command /// Update the input field pub fn add_event( &mut self, w: &mut W, event: TimedEvent, app_state: &AppState, con: &AppContext, ) -> Result { let sel_info = self.states[self.states.len() - 1].sel_info(app_state); let mode = self.state().get_mode(); let panel_state_type = self.state().get_type(); self.input.on_event(w, event, con, sel_info, app_state, mode, panel_state_type) } pub fn push_state(&mut self, new_state: Box) { self.input.set_content(&new_state.get_starting_input()); self.states.push(new_state); } pub fn mut_state(&mut self) -> &mut dyn PanelState { self.states.last_mut().unwrap().as_mut() } pub fn state(&self) -> &dyn PanelState { self.states.last().unwrap().as_ref() } pub fn clear_input(&mut self) { self.input.set_content(""); } /// remove the verb invocation from the input but keep /// the filter if there's one pub fn clear_input_invocation(&mut self, con: &AppContext) { let mut command_parts = CommandParts::from(self.input.get_content()); if command_parts.verb_invocation.is_some() { command_parts.verb_invocation = None; let new_input = format!("{command_parts}"); self.input.set_content(&new_input); } self.mut_state().set_mode(con.initial_mode()); } pub fn set_input_content(&mut self, content: &str) { self.input.set_content(content); } pub fn get_input_content(&self) -> String { self.input.get_content() } /// change the argument of the verb in the input, if there's one pub fn set_input_arg(&mut self, arg: String) { let mut command_parts = CommandParts::from(self.input.get_content()); if let Some(invocation) = &mut command_parts.verb_invocation { invocation.args = Some(arg); let new_input = format!("{command_parts}"); self.input.set_content(&new_input); } } /// return true when the element has been removed pub fn remove_state(&mut self) -> bool { if self.states.len() > 1 { self.states.pop(); self.input.set_content(&self.state().get_starting_input()); true } else { false } } /// render the whole panel (state, status, purpose, input, flags) pub fn display( &mut self, w: &mut W, disc: &DisplayContext, ) -> Result, ProgramError> { self.mut_state().display(w, disc)?; if disc.active || !WIDE_STATUS { self.write_status(w, disc.panel_skin, disc.screen)?; } let mut input_area = self.areas.input.clone(); if disc.active { self.write_purpose(w, disc.panel_skin, disc.screen, disc.con)?; let flags = self.state().get_flags(); let input_content_len = self.input.get_content().len() as u16; let flags_len = flags_display::visible_width(&flags); if input_area.width > input_content_len + 1 + flags_len { input_area.width -= flags_len + 1; disc.screen.goto(w, input_area.left + input_area.width, input_area.top)?; flags_display::write(w, &flags, disc.panel_skin)?; } } let cursor_pos = self.input.display( w, disc.active, self.state().get_mode(), input_area, disc.panel_skin, )?; Ok(cursor_pos) } fn write_status( &self, w: &mut W, panel_skin: &PanelSkin, screen: Screen, ) -> Result<(), ProgramError> { let task = self.state().get_pending_task(); status_line::write( w, task, &self.status, &self.areas.status, panel_skin, screen, ) } /// if a panel has a specific purpose (i.e. is here for /// editing of the verb argument on another panel), render /// a hint of that purpose on screen fn write_purpose( &self, w: &mut W, panel_skin: &PanelSkin, screen: Screen, con: &AppContext, ) -> Result<(), ProgramError> { if !self.purpose.is_arg_edition() { return Ok(()); } if let Some(area) = &self.areas.purpose { let shortcut = con .verb_store .verbs() .iter() .filter(|v| match &v.execution { VerbExecution::Internal(exec) => exec.internal == Internal::start_end_panel, _ => false, }) .filter_map(|v| v.keys.first()) .map(|&k| KEY_FORMAT.to_string(k)) .next() .unwrap_or_else(|| ":start_end_panel".to_string()); let md = format!("hit *{shortcut}* to fill arg "); // Add verbindex in purpose ? screen.goto(w, area.left, area.top)?; panel_skin.purpose_skin.write_composite_fill( w, Composite::from_inline(&md), area.width as usize, Alignment::Right, )?; } Ok(()) } // /// return the last non empty pattern used in a previous state // pub fn last_pattern(&self) -> Option<&InputPattern> { // for state in self.states.iter().rev().skip(1) { // let pattern = state.pattern(); // if pattern.is_some() { // return Some(pattern); // } // } // None // } } broot-1.46.3/src/app/panel_id.rs000064400000000000000000000003021046102023000145270ustar 00000000000000/// The unique identifiant of a panel #[derive(Debug, Clone, Copy, PartialEq)] pub struct PanelId(usize); impl From for PanelId { fn from(u: usize) -> Self { Self(u) } } broot-1.46.3/src/app/panel_purpose.rs000064400000000000000000000006701046102023000156400ustar 00000000000000use { super::SelectionType, }; /// the possible special reason the panel was open #[derive(Debug, Clone, Copy)] pub enum PanelPurpose { None, ArgEdition { arg_type: SelectionType, }, Preview, } impl PanelPurpose { pub fn is_arg_edition(self) -> bool { matches!(self, PanelPurpose::ArgEdition { .. }) } pub fn is_preview(self) -> bool { matches!(self, PanelPurpose::Preview) } } broot-1.46.3/src/app/panel_state.rs000064400000000000000000001220021046102023000152550ustar 00000000000000use { super::*, crate::{ command::*, display::*, errors::ProgramError, flag::Flag, help::HelpState, pattern::*, preview::*, print, stage::*, task_sync::Dam, tree::*, verb::*, }, std::{ path::{Path, PathBuf}, str::FromStr, }, }; /// a panel state, stackable to allow reverting /// to a previous one pub trait PanelState { fn get_type(&self) -> PanelStateType; fn set_mode(&mut self, mode: Mode); fn get_mode(&self) -> Mode; /// called on start of on_command fn clear_pending(&mut self) {} fn on_click( &mut self, _x: u16, _y: u16, _screen: Screen, _con: &AppContext, ) -> Result { Ok(CmdResult::Keep) } fn on_double_click( &mut self, _x: u16, _y: u16, _screen: Screen, _con: &AppContext, ) -> Result { Ok(CmdResult::Keep) } fn on_pattern( &mut self, _pat: InputPattern, _app_state: &AppState, _con: &AppContext, ) -> Result { Ok(CmdResult::Keep) } fn on_mode_verb( &mut self, mode: Mode, con: &AppContext, ) -> CmdResult { if con.modal { self.set_mode(mode); CmdResult::Keep } else { CmdResult::error("modal mode not enabled in configuration") } } /// execute the internal with the optional given invocation. /// /// The invocation comes from the input and may be related /// to a different verb (the verb may have been triggered /// by a key shortcut) #[allow(clippy::too_many_arguments)] fn on_internal( &mut self, w: &mut W, invocation_parser: Option<&InvocationParser>, internal_exec: &InternalExecution, input_invocation: Option<&VerbInvocation>, trigger_type: TriggerType, app_state: &mut AppState, cc: &CmdContext, ) -> Result; /// a generic implementation of on_internal which may be /// called by states when they don't have a specific /// behavior to execute #[allow(clippy::too_many_arguments)] fn on_internal_generic( &mut self, _w: &mut W, invocation_parser: Option<&InvocationParser>, internal_exec: &InternalExecution, input_invocation: Option<&VerbInvocation>, _trigger_type: TriggerType, app_state: &mut AppState, cc: &CmdContext, ) -> Result { let con = &cc.app.con; let screen = cc.app.screen; let bang = input_invocation .map(|inv| inv.bang) .unwrap_or(internal_exec.bang); Ok(match internal_exec.internal { Internal::apply_flags => { debug!("applying flags input_invocation: {:#?}", input_invocation); let flags = input_invocation.and_then(|inv| inv.args.as_ref()); if let Some(flags) = flags { self.with_new_options( screen, &|o| { match o.apply_flags(flags) { Ok(()) => "*flags applied*", Err(e) => e, } }, bang, con, ) } else { CmdResult::error(":apply_flags needs flags as arguments") } } Internal::back => CmdResult::PopState, Internal::copy_line | Internal::copy_path => { #[cfg(not(feature = "clipboard"))] { CmdResult::error("Clipboard feature not enabled at compilation") } #[cfg(feature = "clipboard")] { if let Some(path) = self.selected_path() { let path = path.to_string_lossy().to_string(); match terminal_clipboard::set_string(path) { Ok(()) => CmdResult::Keep, Err(_) => CmdResult::error("Clipboard error while copying path"), } } else { CmdResult::error("Nothing to copy") } } } Internal::close_panel_ok => CmdResult::ClosePanel { validate_purpose: true, panel_ref: PanelReference::Active, clear_cache: true, }, Internal::close_panel_cancel => CmdResult::ClosePanel { validate_purpose: false, panel_ref: PanelReference::Active, clear_cache: false, }, Internal::move_panel_divider => { let MoveDividerArgs { divider, dx } = get_arg( input_invocation, internal_exec, MoveDividerArgs { divider: 0, dx: 1 }, ); CmdResult::ChangeLayout(LayoutInstruction::MoveDivider { divider, dx }) } Internal::default_layout => { CmdResult::ChangeLayout(LayoutInstruction::Clear) } Internal::set_panel_width => { let SetPanelWidthArgs { panel, width } = get_arg( input_invocation, internal_exec, SetPanelWidthArgs { panel: 0, width: 100 }, ); CmdResult::ChangeLayout(LayoutInstruction::SetPanelWidth { panel, width }) } #[cfg(feature = "trash")] Internal::purge_trash => { let res = trash::os_limited::list() .and_then(|items| { trash::os_limited::purge_all(items) }); match res { Ok(()) => CmdResult::RefreshState { clear_cache: false }, Err(e) => CmdResult::DisplayError(format!("{e}")), } } #[cfg(feature = "trash")] Internal::open_trash => { let trash_state = crate::trash::TrashState::new( self.tree_options(), con, ); match trash_state { Ok(state) => { let bang = input_invocation .map(|inv| inv.bang) .unwrap_or(internal_exec.bang); if bang && cc.app.preview_panel.is_none() { CmdResult::NewPanel { state: Box::new(state), purpose: PanelPurpose::None, direction: HDir::Right, } } else { CmdResult::new_state(Box::new(state)) } } Err(e) => CmdResult::DisplayError(format!("{e}")), } } #[cfg(unix)] Internal::filesystems => { let fs_state = crate::filesystems::FilesystemState::new( self.selected_path(), self.tree_options(), con, ); match fs_state { Ok(state) => { let bang = input_invocation .map(|inv| inv.bang) .unwrap_or(internal_exec.bang); if bang && cc.app.preview_panel.is_none() { CmdResult::NewPanel { state: Box::new(state), purpose: PanelPurpose::None, direction: HDir::Right, } } else { CmdResult::new_state(Box::new(state)) } } Err(e) => CmdResult::DisplayError(format!("{e}")), } } Internal::help => { let bang = input_invocation .map(|inv| inv.bang) .unwrap_or(internal_exec.bang); if bang && cc.app.preview_panel.is_none() { CmdResult::NewPanel { state: Box::new(HelpState::new(self.tree_options(), screen, con)), purpose: PanelPurpose::None, direction: HDir::Right, } } else { CmdResult::new_state(Box::new( HelpState::new(self.tree_options(), screen, con) )) } } Internal::mode_input => self.on_mode_verb(Mode::Input, con), Internal::mode_command => self.on_mode_verb(Mode::Command, con), Internal::open_leave => { if let Some(selection) = self.selection() { selection.to_opener(con)? } else { CmdResult::error("no selection to open") } } Internal::open_preview => self.open_preview(None, false, cc), Internal::preview_image => self.open_preview(Some(PreviewMode::Image), false, cc), Internal::preview_text => self.open_preview(Some(PreviewMode::Text), false, cc), Internal::preview_binary => self.open_preview(Some(PreviewMode::Hex), false, cc), Internal::toggle_preview => self.open_preview(None, true, cc), Internal::sort_by_count => self.with_new_options( screen, &|o| { if o.sort == Sort::Count { o.sort = Sort::None; o.show_counts = false; "*not sorting anymore*" } else { o.sort = Sort::Count; o.show_counts = true; "*now sorting by file count*" } }, bang, con, ), Internal::sort_by_date => self.with_new_options( screen, &|o| { if o.sort == Sort::Date { o.sort = Sort::None; o.show_dates = false; "*not sorting anymore*" } else { o.sort = Sort::Date; o.show_dates = true; "*now sorting by last modified date*" } }, bang, con, ), Internal::sort_by_size => self.with_new_options( screen, &|o| { if o.sort == Sort::Size { o.sort = Sort::None; o.show_sizes = false; "*not sorting anymore*" } else { o.sort = Sort::Size; o.show_sizes = true; o.show_root_fs = true; "*now sorting files and directories by total size*" } }, bang, con, ), Internal::sort_by_type => self.with_new_options( screen, &|o| { match o.sort { Sort::TypeDirsFirst => { o.sort = Sort::TypeDirsLast; "*sorting by type, directories last*" } Sort::TypeDirsLast => { o.sort = Sort::None; "*not sorting anymore*" } _ => { o.sort = Sort::TypeDirsFirst; "*sorting by type, directories first*" } } }, bang, con, ), Internal::sort_by_type_dirs_first => self.with_new_options( screen, &|o| { if o.sort == Sort::TypeDirsFirst { o.sort = Sort::None; "*not sorting anymore*" } else { o.sort = Sort::TypeDirsFirst; "*now sorting by type, directories first*" } }, bang, con, ), Internal::sort_by_type_dirs_last => self.with_new_options( screen, &|o| { if o.sort == Sort::TypeDirsLast { o.sort = Sort::None; "*not sorting anymore*" } else { o.sort = Sort::TypeDirsLast; "*now sorting by type, directories last*" } }, bang, con, ), Internal::no_sort => self.with_new_options( screen, &|o| { if o.sort == Sort::None { "*still not searching*" } else { o.sort = Sort::None; "*not sorting anymore*" } }, bang, con, ), Internal::toggle_counts => { self.with_new_options( screen, &|o| { o.show_counts ^= true; if o.show_counts { "*displaying file counts*" } else { "*hiding file counts*" } }, bang, con, ) } Internal::toggle_tree => { self.with_new_options( screen, &|o| { o.show_tree ^= true; if o.show_tree { "*displaying tree structure (if possible)*" } else { "*displaying only current directory*" } }, bang, con, ) } Internal::toggle_dates => { self.with_new_options( screen, &|o| { o.show_dates ^= true; if o.show_dates { "*displaying last modified dates*" } else { "*hiding last modified dates*" } }, bang, con, ) } Internal::toggle_device_id => { self.with_new_options( screen, &|o| { o.show_device_id ^= true; if o.show_device_id { "*displaying device id*" } else { "*hiding device id*" } }, bang, con, ) } Internal::toggle_files => { self.with_new_options( screen, &|o| { o.only_folders ^= true; if o.only_folders { "*displaying only directories*" } else { "*displaying both files and directories*" } }, bang, con, ) } Internal::toggle_hidden => { self.with_new_options( screen, &|o| { o.show_hidden ^= true; if o.show_hidden { "h:**y** - *Hidden files displayed*" } else { "h:**n** - *Hidden files not displayed*" } }, bang, con, ) } Internal::toggle_root_fs => { self.with_new_options( screen, &|o| { o.show_root_fs ^= true; if o.show_root_fs { "*displaying filesystem info for the tree's root directory*" } else { "*removing filesystem info*" } }, bang, con, ) } Internal::set_max_depth => { let args = input_invocation.and_then(|inv| inv.args.as_ref()); if let Some(flags) = args { self.with_new_options( screen, &|o| { if let Ok(max_depth) = flags.parse::() { o.max_depth = Some(max_depth); "*max depth updated*" } else { "*depth must be an integer*" } }, bang, con, ) } else { CmdResult::error(":set_max_depth needs a depth as an argument") } } Internal::unset_max_depth => { self.with_new_options( screen, &|o| { o.max_depth = None; "*cleared max depth*" }, bang, con, ) } Internal::toggle_git_ignore | Internal::toggle_ignore => { self.with_new_options( screen, &|o| { o.respect_git_ignore ^= true; if o.respect_git_ignore { "gi:**y** - *applying gitignore rules*" } else { "gi:**n** - *not applying gitignore rules*" } }, bang, con, ) } Internal::toggle_git_file_info => { self.with_new_options( screen, &|o| { o.show_git_file_info ^= true; if o.show_git_file_info { "*displaying git info next to files*" } else { "*removing git file info*" } }, bang, con, ) } Internal::toggle_git_status => { self.with_new_options( screen, &|o| { if o.filter_by_git_status { o.filter_by_git_status = false; "*not filtering according to git status anymore*" } else { o.filter_by_git_status = true; o.show_hidden = true; "*only displaying new or modified files*" } }, bang, con ) } Internal::toggle_perm => { self.with_new_options( screen, &|o| { o.show_permissions ^= true; if o.show_permissions { "*displaying file permissions*" } else { "*removing file permissions*" } }, bang, con, ) } Internal::toggle_sizes => self.with_new_options( screen, &|o| { if o.show_sizes { o.show_sizes = false; o.show_root_fs = false; "*removing sizes of files and directories*" } else { o.show_sizes = true; o.show_root_fs = true; "*now displaying sizes of files and directories*" } }, bang, con, ), Internal::toggle_trim_root => { self.with_new_options( screen, &|o| { o.trim_root ^= true; if o.trim_root { "*now trimming root from excess files*" } else { "*not trimming root files anymore*" } }, bang, con, ) } Internal::close_preview => { if let Some(id) = cc.app.preview_panel { CmdResult::ClosePanel { validate_purpose: false, panel_ref: PanelReference::Id(id), clear_cache: false, } } else { CmdResult::Keep } } Internal::escape => { CmdResult::HandleInApp(Internal::escape) } Internal::focus_staging_area_no_open => { CmdResult::HandleInApp(Internal::focus_staging_area_no_open) } // panel_left depends on the kind of panel and is usually handled // in a specific state, contrary to panel_left_no_open Internal::panel_left | Internal::panel_left_no_open => { CmdResult::HandleInApp(Internal::panel_left_no_open) } // panel_right depends on the kind of panel and is usually handled // in a specific state, contrary to panel_right_no_open Internal::panel_right | Internal::panel_right_no_open => { CmdResult::HandleInApp(Internal::panel_right_no_open) } Internal::toggle_second_tree => { CmdResult::HandleInApp(Internal::toggle_second_tree) } Internal::clear_stage => { app_state.stage.clear(); if let Some(panel_id) = cc.app.stage_panel { CmdResult::ClosePanel { validate_purpose: false, panel_ref: PanelReference::Id(panel_id), clear_cache: false, } } else { CmdResult::Keep } } Internal::stage => self.stage(app_state, cc, con), Internal::unstage => self.unstage(app_state, cc, con), Internal::toggle_stage => self.toggle_stage(app_state, cc, con), Internal::close_staging_area => { if let Some(id) = cc.app.stage_panel { CmdResult::ClosePanel { validate_purpose: false, panel_ref: PanelReference::Id(id), clear_cache: false, } } else { CmdResult::Keep } } Internal::open_staging_area => { if cc.app.stage_panel.is_none() { CmdResult::NewPanel { state: Box::new(StageState::new(app_state, self.tree_options(), con)), purpose: PanelPurpose::None, direction: HDir::Right, } } else { CmdResult::Keep } } Internal::toggle_staging_area => { if let Some(id) = cc.app.stage_panel { CmdResult::ClosePanel { validate_purpose: false, panel_ref: PanelReference::Id(id), clear_cache: false, } } else { CmdResult::NewPanel { state: Box::new(StageState::new(app_state, self.tree_options(), con)), purpose: PanelPurpose::None, direction: HDir::Right, } } } Internal::set_syntax_theme => CmdResult::HandleInApp(Internal::set_syntax_theme), Internal::print_path => print::print_paths(self.sel_info(app_state), con)?, Internal::print_relative_path => print::print_relative_paths(self.sel_info(app_state), con)?, Internal::refresh => CmdResult::RefreshState { clear_cache: true }, Internal::quit => CmdResult::Quit, Internal::clear_output => { verb_clear_output(con) .unwrap_or_else(|e| CmdResult::DisplayError(format!("{e}"))) } Internal::write_output => { let sel_info = self.sel_info(app_state); let exec_builder = match input_invocation { Some(inv) => { ExecutionStringBuilder::with_invocation( invocation_parser, sel_info, app_state, inv.args.as_ref(), ) } None => { ExecutionStringBuilder::without_invocation(sel_info, app_state) } }; if let Some(pattern) = internal_exec.arg.as_ref() { let line = exec_builder.string(pattern, con); verb_write(con, &line)?; } else { let line = input_invocation .and_then(|inv| inv.args.as_ref()) .map_or("", String::as_str); verb_write(con, line)?; } CmdResult::Keep } internal if internal.is_input_related() => { CmdResult::HandleInApp(internal) } _ => CmdResult::Keep, }) } fn stage( &self, app_state: &mut AppState, cc: &CmdContext, con: &AppContext, ) -> CmdResult { if let Some(path) = self.selected_path() { let path = path.to_path_buf(); app_state.stage.add(path); if cc.app.stage_panel.is_none() { return CmdResult::NewPanel { state: Box::new(StageState::new(app_state, self.tree_options(), con)), purpose: PanelPurpose::None, direction: HDir::Right, }; } } else { // TODO display error ? warn!("no path in state"); } CmdResult::Keep } fn unstage( &self, app_state: &mut AppState, cc: &CmdContext, _con: &AppContext, ) -> CmdResult { if let Some(path) = self.selected_path() { if app_state.stage.remove(path) && app_state.stage.is_empty() { if let Some(panel_id) = cc.app.stage_panel { return CmdResult::ClosePanel { validate_purpose: false, panel_ref: PanelReference::Id(panel_id), clear_cache: false, }; } } } CmdResult::Keep } fn toggle_stage( &self, app_state: &mut AppState, cc: &CmdContext, con: &AppContext, ) -> CmdResult { if let Some(path) = self.selected_path() { if app_state.stage.contains(path) { self.unstage(app_state, cc, con) } else { self.stage(app_state, cc, con) } } else { CmdResult::error("no selection") } } fn execute_verb( &mut self, w: &mut W, // needed because we may want to switch from alternate in some externals verb: &Verb, invocation: Option<&VerbInvocation>, trigger_type: TriggerType, app_state: &mut AppState, cc: &CmdContext, ) -> Result { if verb.needs_selection && !self.has_at_least_one_selection(app_state) { return Ok(CmdResult::error("This verb needs a selection")); } if verb.needs_another_panel && app_state.other_panel_path.is_none() { return Ok(CmdResult::error("This verb needs another panel")); } let res = match &verb.execution { VerbExecution::Internal(internal_exec) => { self.on_internal( w, verb.invocation_parser.as_ref(), internal_exec, invocation, trigger_type, app_state, cc, ) } VerbExecution::External(external) => { self.execute_external(w, verb, external, invocation, app_state, cc) } VerbExecution::Sequence(seq_ex) => { self.execute_sequence(w, verb, seq_ex, invocation, app_state, cc) } }; if res.is_ok() { // if the stage has been emptied by the operation (eg a "rm"), we // close it app_state.stage.refresh(); if app_state.stage.is_empty() { if let Some(id) = cc.app.stage_panel { return Ok(CmdResult::ClosePanel { validate_purpose: false, panel_ref: PanelReference::Id(id), clear_cache: true, }); } } } res } fn execute_external( &mut self, w: &mut W, verb: &Verb, external_execution: &ExternalExecution, invocation: Option<&VerbInvocation>, app_state: &mut AppState, cc: &CmdContext, ) -> Result { let sel_info = self.sel_info(app_state); if let Some(invocation) = &invocation { if let Some(error) = verb.check_args(sel_info, invocation, &app_state.other_panel_path) { debug!("verb.check_args prevented execution: {:?}", &error); return Ok(CmdResult::error(error)); } } let exec_builder = ExecutionStringBuilder::with_invocation( verb.invocation_parser.as_ref(), sel_info, app_state, if let Some(inv) = invocation { inv.args.as_ref() } else { None }, ); external_execution.to_cmd_result(w, exec_builder, cc.app.con) } fn execute_sequence( &mut self, _w: &mut W, verb: &Verb, seq_ex: &SequenceExecution, invocation: Option<&VerbInvocation>, app_state: &mut AppState, cc: &CmdContext, ) -> Result { let sel_info = self.sel_info(app_state); if matches!(sel_info, SelInfo::More(_)) { // sequences would be hard to execute as the execution on a file can change the // state in too many ways (changing selection, focused panel, parent, unstage or // stage files, removing the staged paths, etc.) return Ok(CmdResult::error("sequences can't be executed on multiple selections")); } let exec_builder = ExecutionStringBuilder::with_invocation( verb.invocation_parser.as_ref(), sel_info, app_state, if let Some(inv) = invocation { inv.args.as_ref() } else { None }, ); let sequence = exec_builder.sequence( &seq_ex.sequence, &cc.app.con.verb_store, cc.app.con, Some(self.get_type()), ); Ok(CmdResult::ExecuteSequence { sequence }) } /// change the state, does no rendering fn on_command( &mut self, w: &mut W, app_state: &mut AppState, cc: &CmdContext, ) -> Result { self.clear_pending(); let con = &cc.app.con; let screen = cc.app.screen; match &cc.cmd { Command::Click(x, y) => self.on_click(*x, *y, screen, con), Command::DoubleClick(x, y) => self.on_double_click(*x, *y, screen, con), Command::PatternEdit { raw, expr } => { match InputPattern::new(raw.clone(), expr, con) { Ok(pattern) => self.on_pattern(pattern, app_state, con), Err(e) => Ok(CmdResult::DisplayError(format!("{e}"))), } } Command::VerbTrigger { verb_id, input_invocation, } => self.execute_verb( w, con.verb_store.verb(*verb_id), input_invocation.as_ref(), TriggerType::Other, app_state, cc, ), Command::Internal { internal, input_invocation, } => self.on_internal( w, None, &InternalExecution::from_internal(*internal), input_invocation.as_ref(), TriggerType::Other, app_state, cc, ), Command::VerbInvocate(invocation) => { let sel_info = self.sel_info(app_state); match con.verb_store.search_sel_info( &invocation.name, sel_info, Some(self.get_type()), ) { PrefixSearchResult::Match(_, verb) => { self.execute_verb( w, verb, Some(invocation), TriggerType::Input(verb), app_state, cc, ) } _ => Ok(CmdResult::verb_not_found(&invocation.name)), } } Command::None | Command::VerbEdit(_) => { // we do nothing here, the real job is done in get_status Ok(CmdResult::Keep) } } } /// return a cmdresult asking for the opening of a preview fn open_preview( &mut self, preferred_mode: Option, close_if_open: bool, cc: &CmdContext, ) -> CmdResult { if let Some(id) = cc.app.preview_panel { if close_if_open { CmdResult::ClosePanel { validate_purpose: false, panel_ref: PanelReference::Id(id), clear_cache: false, } } else if preferred_mode.is_some() { // we'll make the preview mode change be // applied on the preview panel CmdResult::ApplyOnPanel { id } } else { CmdResult::Keep } } else if let Some(path) = self.selected_path() { CmdResult::NewPanel { state: Box::new(PreviewState::new( path.to_path_buf(), InputPattern::none(), preferred_mode, self.tree_options(), cc.app.con, )), purpose: PanelPurpose::Preview, direction: HDir::Right, } } else { CmdResult::error("no selected file") } } /// must return None if the state doesn't display a file tree fn tree_root(&self) -> Option<&Path> { None } fn selected_path(&self) -> Option<&Path>; fn selection(&self) -> Option>; fn sel_info<'c>(&'c self, _app_state: &'c AppState) -> SelInfo<'c> { // overloaded in stage_state match self.selection() { None => SelInfo::None, Some(selection) => SelInfo::One(selection), } } fn has_at_least_one_selection(&self, _app_state: &AppState) -> bool { true // overloaded in stage_state } fn refresh(&mut self, screen: Screen, con: &AppContext) -> Command; fn tree_options(&self) -> TreeOptions; /// Build a cmdResult in response to a command being a change of /// tree options. This may or not be a new state. /// /// The provided `change_options` function returns a status message /// explaining the change fn with_new_options( &mut self, screen: Screen, change_options: &dyn Fn(&mut TreeOptions) -> &'static str, in_new_panel: bool, con: &AppContext, ) -> CmdResult; fn do_pending_task( &mut self, _app_state: &mut AppState, _screen: Screen, _con: &AppContext, _dam: &mut Dam, ) -> Result<(), ProgramError> { // no pending task in default impl unreachable!(); } fn get_pending_task( &self, ) -> Option<&'static str> { None } fn display( &mut self, w: &mut W, disc: &DisplayContext, ) -> Result<(), ProgramError>; /// return the flags to display fn get_flags(&self) -> Vec { vec![] } fn get_starting_input(&self) -> String { String::new() } fn set_selected_path(&mut self, _path: PathBuf, _con: &AppContext) { // this function is useful for preview states } /// return the status which should be used when there's no verb edited fn no_verb_status( &self, _has_previous_state: bool, _con: &AppContext, _width: usize, // available width ) -> Status { Status::from_message( "Hit *esc* to get back, or a space to start a verb" ) } fn get_status( &self, app_state: &AppState, cc: &CmdContext, has_previous_state: bool, width: usize, ) -> Status { match &cc.cmd { Command::PatternEdit { .. } => self.no_verb_status(has_previous_state, cc.app.con, width), Command::VerbEdit(invocation) | Command::VerbTrigger { input_invocation: Some(invocation), .. } => { if invocation.name.is_empty() { Status::new( "Type a verb then *enter* to execute it (*?* for the list of verbs)", false, ) } else { let sel_info = self.sel_info(app_state); match cc.app.con.verb_store.search_sel_info( &invocation.name, sel_info, Some(self.get_type()), ) { PrefixSearchResult::NoMatch => { Status::new("No matching verb (*?* for the list of verbs)", true) } PrefixSearchResult::Match(_, verb) => { self.get_verb_status(verb, invocation, sel_info, cc, app_state) } PrefixSearchResult::Matches(completions) => Status::new( format!( "Possible verbs: {}", completions .iter() .map(|c| format!("*{c}*")) .collect::>() .join(", "), ), false, ), } } } _ => self.no_verb_status(has_previous_state, cc.app.con, width), } } fn get_verb_status( &self, verb: &Verb, invocation: &VerbInvocation, sel_info: SelInfo<'_>, cc: &CmdContext, app_state: &AppState, ) -> Status { if sel_info.count_paths() > 1 { if let VerbExecution::External(external) = &verb.execution { if external.exec_mode != ExternalExecutionMode::StayInBroot { return Status::new( "only verbs returning to broot on end can be executed on a multi-selection".to_owned(), true, ); } } // right now there's no check for sequences but they're inherently dangerous } if let Some(err) = verb.check_args(sel_info, invocation, &app_state.other_panel_path) { Status::new(err, true) } else { Status::new( verb.get_status_markdown( sel_info, app_state, invocation, cc.app.con, ), false, ) } } } pub fn get_arg( verb_invocation: Option<&VerbInvocation>, internal_exec: &InternalExecution, default: T, ) -> T { verb_invocation .and_then(|vi| vi.args.as_ref()) .or(internal_exec.arg.as_ref()) .and_then(|s| s.parse::().ok()) .unwrap_or(default) } broot-1.46.3/src/app/selection.rs000064400000000000000000000117701046102023000147540ustar 00000000000000use { super::{ AppContext, CmdResult, }, crate::{ errors::ProgramError, launchable::Launchable, stage::Stage, verb::FileTypeCondition, }, std::{ fs::OpenOptions, io::Write, path::Path, }, }; /// the id of a line, starting at 1 /// (0 if not specified) pub type LineNumber = usize; #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum SelectionType { File, Directory, Any, } /// light information about the currently selected /// file and maybe line number #[derive(Debug, Clone, Copy)] pub struct Selection<'s> { pub path: &'s Path, pub line: LineNumber, // the line number in the file (0 if none selected) pub stype: SelectionType, pub is_exe: bool, } #[derive(Debug, Clone, Copy)] pub enum SelInfo<'s> { None, One(Selection<'s>), More(&'s Stage), // by contract the stage contains at least 2 paths } impl SelectionType { pub fn respects(self, constraint: Self) -> bool { constraint == Self::Any || self == constraint } pub fn is_respected_by(self, sel_type: Option) -> bool { match (self, sel_type) { (Self::File, Some(Self::File)) => true, (Self::Directory, Some(Self::Directory)) => true, (Self::Any, _) => true, _ => false, } } pub fn from(path: &Path) -> Self { if path.is_dir() { Self::Directory } else { Self::File } } } impl Selection<'_> { /// build a CmdResult with a launchable which will be used to /// open the relevant file the best possible way pub fn to_opener( self, con: &AppContext, ) -> Result { Ok(if self.is_exe { let path = self.path.to_string_lossy().to_string(); if let Some(export_path) = &con.launch_args.outcmd { // broot was launched as br, we can launch the executable from the shell let f = OpenOptions::new().append(true).open(export_path)?; writeln!(&f, "{path}")?; CmdResult::Quit } else { CmdResult::from(Launchable::program( vec![path], None, // we don't set the working directory true, // we switch the terminal during execution con, )?) } } else { CmdResult::from(Launchable::opener(self.path.to_path_buf())) }) } } impl<'a> SelInfo<'a> { pub fn from_path(path: &'a Path) -> Self { Self::One( Selection { stype: SelectionType::from(path), line: 0, path, is_exe: false, // OK, I don't know } ) } pub fn count_paths(&self) -> usize { match self { SelInfo::None => 0, SelInfo::One(_) => 1, SelInfo::More(stage) => stage.len(), } } pub fn is_accepted_by(&self, condition: FileTypeCondition) -> bool { match self { SelInfo::None => true, SelInfo::One(sel) => condition.accepts_path(sel.path), SelInfo::More(stage) => { for path in stage.paths() { if !condition.accepts_path(path) { return false; } } true } } } pub fn common_stype(&self) -> Option { match self { SelInfo::None => None, SelInfo::One(sel) => Some(sel.stype), SelInfo::More(stage) => { let stype = SelectionType::from(&stage.paths()[0]); for path in stage.paths().iter().skip(1) { if stype != SelectionType::from(path) { return None; } } Some(stype) } } } pub fn one_sel(self) -> Option> { match self { SelInfo::One(sel) => Some(sel), _ => None, } } pub fn one_path(self) -> Option<&'a Path> { self.one_sel().map(|sel| sel.path) } pub fn extension(&self) -> Option<&str> { match self { SelInfo::None => None, SelInfo::One(sel) => sel.path.extension().and_then(|e| e.to_str()), SelInfo::More(stage) => { let common_extension = stage.paths()[0] .extension().and_then(|e| e.to_str()); #[allow(clippy::question_mark)] if common_extension.is_none() { return None; } for path in stage.paths().iter().skip(1) { let extension = path.extension().and_then(|e| e.to_str()); if extension != common_extension { return None; } } common_extension } } } } broot-1.46.3/src/app/standard_status.rs000064400000000000000000000211441046102023000161660ustar 00000000000000use { super::*, crate::{ verb::{ Internal, VerbStore, }, }, }; /// All the precomputed status which don't involve a verb pub struct StandardStatus { tree_top_focus: String, // go up (if not at root) tree_dir_focus: String, tree_dir_cd: Option, // TODO check outcmd tree_file_open_stay: Option, tree_file_open_stay_long: Option, tree_file_open_leave: Option, tree_unfiltered: String, tree_filtered: String, preview_unfiltered: String, // ctrl-left to close, or a pattern to filter preview_filtered: Option, preview_restorable_filter: Option, not_first_state: String, // "esc to go back" help: String, no_verb: String, pub all_files_hidden: Option, pub all_files_ignored: Option, } impl StandardStatus { pub fn new(verb_store: &VerbStore) -> Self { let tree_top_focus = "*enter* to go up".to_string(); // enter is hardcoded on focus let tree_dir_focus = "*enter* to focus".to_string(); let tree_dir_cd = verb_store .key_desc_of_internal_stype(Internal::open_leave, SelectionType::Directory) .map(|k| format!("*{k}* to cd")); let tree_file_open_stay = verb_store .key_desc_of_internal_stype(Internal::open_stay, SelectionType::File) .map(|k| format!("*{k}* to open")); let tree_file_open_stay_long = verb_store .key_desc_of_internal_stype(Internal::open_stay, SelectionType::File) .map(|k| format!("*{k}* to open the file")); let tree_file_open_leave = verb_store .key_desc_of_internal_stype(Internal::open_leave, SelectionType::File) .map(|k| format!("*{k}* to open and quit")); //let tree_file_enter = None; // TODO (for when enter is customized) let tree_unfiltered = "a few letters to search".to_string(); let tree_filtered = "*esc* to clear the filter".to_string(); let preview_unfiltered = "a pattern to filter".to_string(); let preview_filtered = verb_store .key_desc_of_internal(Internal::panel_right) .map(|k| format!("*{k}* to reveal the text")); let preview_restorable_filter = verb_store .key_desc_of_internal(Internal::panel_left_no_open) .map(|k| format!("*{k}* to restore the filter")); let not_first_state = "*esc* to go back".to_string(); let help = "*?* for help".to_string(); let no_verb = "a space then a verb".to_string(); let all_files_hidden = verb_store .key_desc_of_internal(Internal::toggle_hidden) .map(|k| format!("Some files are hidden, use *{k}* to display them")); let all_files_ignored = verb_store .key_desc_of_internal(Internal::toggle_ignore) .or(verb_store.key_desc_of_internal(Internal::toggle_git_ignore)) .map(|k| format!("Some files are ignored, use *{k}* to display them")); Self { tree_top_focus, tree_dir_focus, tree_dir_cd, tree_file_open_stay, tree_file_open_stay_long, tree_file_open_leave, //tree_file_enter, tree_unfiltered, tree_filtered, preview_unfiltered, preview_filtered, preview_restorable_filter, not_first_state, help, no_verb, all_files_hidden, all_files_ignored, } } pub fn builder<'s>( &'s self, state_type: PanelStateType, selection: Selection<'s>, width: usize, // available width ) -> StandardStatusBuilder<'s> { StandardStatusBuilder::new(self, state_type, selection, width) } } #[derive(Default)] struct StatusParts<'b> { md_parts: Vec<&'b str>, } impl<'b> StatusParts<'b> { fn add(&mut self, md: &'b str) { self.md_parts.push(md); } fn addo(&mut self, md: &'b Option) { if let Some(md) = md { self.md_parts.push(md); } } fn len(&self) -> usize { self.md_parts.len() } /// Build the markdown of the complete status by combining parts /// while not going much over the available width so that we /// don't have too much elision (otherwise it would be too hard to read) fn to_status(&self, available_width: usize) -> Status { let mut md = String::new(); // notes about the truncation: // - in case of truncation, we don't use the long ", or " // separator. It's OK, assuming truncation is only for // when the screen is very small, and not the standard case. let mut sum_len = 0; let max_len = available_width + 3; for (i, p) in self.md_parts.iter().enumerate() { let sep = if i == 0 { "Hit " } else if i == self.md_parts.len() - 1 { ", or " } else { ", " }; sum_len += sep.len() + p.len() - 2; // -2 is an estimate of hidden chars if i > 0 && sum_len > max_len { break; } md.push_str(sep); md.push_str(p); } Status::from_message(md) } } pub struct StandardStatusBuilder<'s> { ss: &'s StandardStatus, state_type: PanelStateType, selection: Selection<'s>, pub has_previous_state: bool, pub is_filtered: bool, pub has_removed_pattern: bool, pub on_tree_root: bool, // should this be part of the Selection struct ? pub width: usize, // available width } impl<'s> StandardStatusBuilder<'s> { fn new( ss: &'s StandardStatus, state_type: PanelStateType, selection: Selection<'s>, width: usize, // available width ) -> Self { Self { ss, state_type, selection, has_previous_state: true, is_filtered: false, has_removed_pattern: false, on_tree_root: false, width, } } pub fn status(self) -> Status { let ss = &self.ss; let mut parts = StatusParts::default(); if self.has_previous_state && !self.is_filtered { parts.add(&ss.not_first_state); } match self.state_type { PanelStateType::Tree => { if self.on_tree_root { if self.selection.path.file_name().is_some() { // it's not '/' parts.add(&ss.tree_top_focus); } } else if self.selection.stype == SelectionType::Directory { parts.add(&ss.tree_dir_focus); parts.addo(&ss.tree_dir_cd); } else if self.selection.stype == SelectionType::File { // maybe add "ctrl-right to preview" ? Or just sometimes ? // (need check no preview) if self.width > 105 { parts.addo(&ss.tree_file_open_stay_long); } else { parts.addo(&ss.tree_file_open_stay); } parts.addo(&ss.tree_file_open_leave); } if self.is_filtered { parts.add(&ss.tree_filtered); } if parts.len() < 3 { parts.add(&ss.help); } if parts.len() < 4 { if self.on_tree_root && !self.is_filtered { parts.add(&ss.tree_unfiltered); } else { parts.add(&ss.no_verb); } } } PanelStateType::Preview => { if self.is_filtered { parts.addo(&ss.preview_filtered); } else if self.has_removed_pattern { parts.addo(&ss.preview_restorable_filter); } else { parts.add(&ss.preview_unfiltered); } parts.add(&ss.no_verb); } PanelStateType::Help => { // not yet used, help_state has its own hard status if parts.len() < 4 { parts.add(&ss.no_verb); } } PanelStateType::Fs => { // TODO fs status } PanelStateType::Stage => { // TODO stage status } PanelStateType::Trash => { // TODO stage status ? Maybe the shortcuts to restore or delete ? } } parts.to_status(self.width) } } broot-1.46.3/src/app/state_type.rs000064400000000000000000000007721046102023000151500ustar 00000000000000use { serde::{Deserialize, Serialize}, }; /// one of the types of state that you could /// find in a panel today #[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "snake_case")] pub enum PanelStateType { /// filesystems Fs, /// help "screen" Help, /// preview panel, never alone on screen Preview, /// stage panel, never alone on screen Stage, /// content of the trash Trash, /// standard browsing tree Tree, } broot-1.46.3/src/app/status.rs000064400000000000000000000013331046102023000143040ustar 00000000000000 /// the status contains information written on the grey line /// near the bottom of the screen #[derive(Debug, Clone)] pub struct Status { pub message: String, // markdown pub error: bool, // is the current message an error? } impl Status { pub fn new>(message: S, error: bool) -> Status { Self { message: message.into(), error, } } pub fn from_message>(message: S) -> Status { Self { message: message.into(), error: false, } } pub fn from_error>(message: S) -> Status { Self { message: message.into(), error: true, } } } broot-1.46.3/src/browser/browser_state.rs000064400000000000000000000767451046102023000165720ustar 00000000000000use { crate::{ app::*, command::*, display::*, errors::{ProgramError, TreeBuildError}, flag::Flag, git, pattern::*, path::{self, PathAnchor}, print, stage::*, task_sync::Dam, tree::*, tree_build::TreeBuilder, verb::*, }, opener, std::path::{Path, PathBuf}, }; /// An application state dedicated to displaying a tree. /// It's the first and main screen of broot. pub struct BrowserState { pub tree: Tree, pub filtered_tree: Option, mode: Mode, // whether we're in 'input' or 'normal' mode pending_task: Option, // note: there are some other pending task, see } /// A task that can be computed in background #[derive(Debug)] enum BrowserTask { Search { pattern: InputPattern, total: bool, }, StageAll { pattern: InputPattern, file_type_condition: FileTypeCondition, }, } impl BrowserState { /// build a new tree state if there's no error and there's no cancellation. pub fn new( path: PathBuf, mut options: TreeOptions, screen: Screen, con: &AppContext, dam: &Dam, ) -> Result { // on windows, canonicalize the path produces UNC paths, so we don't do it. // On other platforms, it's a desirable step, mainly because it simplifies the // paths you'd get for example when focusing a relative symlink containing "..". #[cfg(not(target_os = "windows"))] let path = path.canonicalize().unwrap_or(path); let pending_task = options.pattern .take() .as_option() .map(|pattern| BrowserTask::Search { pattern, total: false }); let builder = TreeBuilder::from( path, options, BrowserState::page_height(screen), con, )?; let tree = builder.build_tree(false, dam)?; Ok(BrowserState { tree, filtered_tree: None, mode: con.initial_mode(), pending_task, }) } fn search(&mut self, pattern: InputPattern, total: bool) { self.pending_task = Some(BrowserTask::Search { pattern, total }); } /// build a cmdResult asking for the addition of a new state /// being a browser state similar to the current one but with /// different options or a different root, or both fn modified( &self, screen: Screen, root: PathBuf, options: TreeOptions, message: Option<&'static str>, in_new_panel: bool, con: &AppContext, ) -> CmdResult { let tree = self.displayed_tree(); let mut new_state = BrowserState::new(root, options, screen, con, &Dam::unlimited()); if let Ok(bs) = &mut new_state { if tree.selection != 0 { bs.displayed_tree_mut().try_select_path(&tree.selected_line().path); } } CmdResult::from_optional_browser_state( new_state, message, in_new_panel, ) } pub fn root(&self) -> &Path { self.tree.root() } pub fn page_height(screen: Screen) -> usize { screen.height as usize - 2 // br shouldn't be displayed when the screen is smaller } /// return a reference to the currently displayed tree, which /// is the filtered tree if there's one, the base tree if not. pub fn displayed_tree(&self) -> &Tree { self.filtered_tree.as_ref().unwrap_or(&self.tree) } /// return a mutable reference to the currently displayed tree, which /// is the filtered tree if there's one, the base tree if not. pub fn displayed_tree_mut(&mut self) -> &mut Tree { self.filtered_tree.as_mut().unwrap_or(&mut self.tree) } pub fn open_selection_stay_in_broot( &mut self, screen: Screen, con: &AppContext, in_new_panel: bool, keep_pattern: bool, ) -> Result { let tree = self.displayed_tree(); let line = tree.selected_line(); let mut target = line.target().to_path_buf(); if line.is_dir() { if tree.selection == 0 { // opening the root would be going to where we already are. // We go up one level instead if let Some(parent) = target.parent() { target = PathBuf::from(parent); } } let dam = Dam::unlimited(); Ok(CmdResult::from_optional_browser_state( BrowserState::new( target, if keep_pattern { tree.options.clone() } else { tree.options.without_pattern() }, screen, con, &dam, ), None, in_new_panel, )) } else { match opener::open(&target) { Ok(exit_status) => { info!("open returned with exit_status {:?}", exit_status); Ok(CmdResult::Keep) } Err(e) => Ok(CmdResult::error(format!("{e:?}"))), } } } pub fn go_to_parent( &mut self, screen: Screen, con: &AppContext, in_new_panel: bool, ) -> CmdResult { match &self.displayed_tree().selected_line().path.parent() { Some(path) => CmdResult::from_optional_browser_state( BrowserState::new( path.to_path_buf(), self.displayed_tree().options.without_pattern(), screen, con, &Dam::unlimited(), ), None, in_new_panel, ), None => CmdResult::error("no parent found"), } } } impl PanelState for BrowserState { fn tree_root(&self) -> Option<&Path> { Some(self.root()) } fn get_type(&self) -> PanelStateType { PanelStateType::Tree } fn set_mode(&mut self, mode: Mode) { self.mode = mode; } fn get_mode(&self) -> Mode { self.mode } fn get_pending_task(&self) -> Option<&'static str> { if self.displayed_tree().has_dir_missing_sum() { Some("computing stats") } else if self.displayed_tree().is_missing_git_status_computation() { Some("computing git status") } else { self .pending_task.as_ref().map(|task| match task { BrowserTask::Search{ .. } => "searching", BrowserTask::StageAll{ .. } => "staging", }) } } fn selected_path(&self) -> Option<&Path> { Some(&self.displayed_tree().selected_line().path) } fn selection(&self) -> Option> { let tree = self.displayed_tree(); let mut selection = tree.selected_line().as_selection(); selection.line = tree.options.pattern.pattern .get_match_line_count(selection.path) .unwrap_or(0); Some(selection) } fn tree_options(&self) -> TreeOptions { self.displayed_tree().options.clone() } /// build a cmdResult asking for the addition of a new state /// being a browser state similar to the current one but with /// different options fn with_new_options( &mut self, screen: Screen, change_options: &dyn Fn(&mut TreeOptions) -> &'static str, in_new_panel: bool, con: &AppContext, ) -> CmdResult { let tree = self.displayed_tree(); let mut options = tree.options.clone(); let message = change_options(&mut options); let message = Some(message); self.modified( screen, tree.root().clone(), options, message, in_new_panel, con, ) } fn clear_pending(&mut self) { self.pending_task = None; } fn on_click( &mut self, _x: u16, y: u16, _screen: Screen, _con: &AppContext, ) -> Result { self.displayed_tree_mut().try_select_y(y as usize); Ok(CmdResult::Keep) } fn on_double_click( &mut self, _x: u16, y: u16, screen: Screen, con: &AppContext, ) -> Result { if self.displayed_tree().selection == y as usize { self.open_selection_stay_in_broot(screen, con, false, false) } else { // A double click always come after a simple click at // same position. If it's not the selected line, it means // the click wasn't on a selectable/openable tree line Ok(CmdResult::Keep) } } fn on_pattern( &mut self, pat: InputPattern, _app_state: &AppState, _con: &AppContext, ) -> Result { if pat.is_none() { self.filtered_tree = None; } if let Some(filtered_tree) = &self.filtered_tree { if pat != filtered_tree.options.pattern { self.search(pat, false); } } else { self.search(pat, false); } Ok(CmdResult::Keep) } fn on_internal( &mut self, w: &mut W, invocation_parser: Option<&InvocationParser>, internal_exec: &InternalExecution, input_invocation: Option<&VerbInvocation>, trigger_type: TriggerType, app_state: &mut AppState, cc: &CmdContext, ) -> Result { debug!("browser_state on_internal {:?}", internal_exec); let con = &cc.app.con; let screen = cc.app.screen; let page_height = BrowserState::page_height(cc.app.screen); let bang = input_invocation .map(|inv| inv.bang) .unwrap_or(internal_exec.bang); Ok(match internal_exec.internal { Internal::back => { if let Some(filtered_tree) = &self.filtered_tree { let filtered_selection = &filtered_tree.selected_line().path; if self.tree.try_select_path(filtered_selection) { self.tree.make_selection_visible(page_height); } self.filtered_tree = None; CmdResult::Keep } else if self.tree.selection > 0 { self.tree.selection = 0; CmdResult::Keep } else { CmdResult::PopState } } Internal::focus => { let tree = self.displayed_tree(); let mut path = &tree.selected_line().path; let parent; if tree.is_root_selected() { if let Some(parent_path) = path.parent() { parent = parent_path.to_path_buf(); path = &parent; } } internal_focus::on_internal( internal_exec, input_invocation, trigger_type, path, tree.options.clone(), app_state, cc, ) } Internal::line_down => { let count = get_arg(input_invocation, internal_exec, 1); self.displayed_tree_mut().move_selection(count, page_height, true); CmdResult::Keep } Internal::line_down_no_cycle => { let count = get_arg(input_invocation, internal_exec, 1); self.displayed_tree_mut().move_selection(count, page_height, false); CmdResult::Keep } Internal::line_up => { let count = get_arg(input_invocation, internal_exec, 1); self.displayed_tree_mut().move_selection(-count, page_height, true); CmdResult::Keep } Internal::line_up_no_cycle => { let count = get_arg(input_invocation, internal_exec, 1); self.displayed_tree_mut().move_selection(-count, page_height, false); CmdResult::Keep } Internal::next_dir => { self.displayed_tree_mut().try_select_next_filtered( TreeLine::is_dir, page_height, ); CmdResult::Keep } Internal::next_match => { self.displayed_tree_mut().try_select_next_filtered( |line| line.direct_match, page_height, ); CmdResult::Keep } Internal::next_same_depth => { self.displayed_tree_mut().try_select_next_same_depth(page_height); CmdResult::Keep } Internal::open_stay => self.open_selection_stay_in_broot(screen, con, bang, false)?, Internal::open_stay_filter => self.open_selection_stay_in_broot(screen, con, bang, true)?, Internal::page_down => { let tree = self.displayed_tree_mut(); if !tree.try_scroll(page_height as i32, page_height) { tree.try_select_last(page_height); } CmdResult::Keep } Internal::page_up => { let tree = self.displayed_tree_mut(); if !tree.try_scroll(-(page_height as i32), page_height) { tree.try_select_first(); } CmdResult::Keep } Internal::panel_left => { let areas = &cc.panel.areas; if areas.is_first() && areas.nb_pos < con.max_panels_count { // we ask for the creation of a panel to the left internal_focus::new_panel_on_path( self.displayed_tree().selected_line().path.to_path_buf(), screen, self.displayed_tree().options.clone(), PanelPurpose::None, con, HDir::Left, ) } else { // we let the app handle other cases CmdResult::HandleInApp(Internal::panel_left_no_open) } } Internal::panel_left_no_open => CmdResult::HandleInApp(Internal::panel_left_no_open), Internal::panel_right => { let areas = &cc.panel.areas; let selected_path = &self.displayed_tree().selected_line().path; if areas.is_last() && areas.nb_pos < con.max_panels_count { let purpose = if selected_path.is_file() && cc.app.preview_panel.is_none() { PanelPurpose::Preview } else { PanelPurpose::None }; // we ask for the creation of a panel to the right internal_focus::new_panel_on_path( selected_path.to_path_buf(), screen, self.displayed_tree().options.clone(), purpose, con, HDir::Right, ) } else { // we ask the app to handle other cases : // focus the panel to the right or close the leftest one CmdResult::HandleInApp(Internal::panel_right_no_open) } } Internal::panel_right_no_open => CmdResult::HandleInApp(Internal::panel_right_no_open), Internal::parent => self.go_to_parent(screen, con, bang), Internal::previous_dir => { self.displayed_tree_mut().try_select_previous_filtered( TreeLine::is_dir, page_height, ); CmdResult::Keep } Internal::previous_match => { self.displayed_tree_mut().try_select_previous_filtered( |line| line.direct_match, page_height, ); CmdResult::Keep } Internal::previous_same_depth => { self.displayed_tree_mut().try_select_previous_same_depth(page_height); CmdResult::Keep } Internal::print_tree => { print::print_tree(self.displayed_tree(), cc.app.screen, cc.app.panel_skin, con)? } Internal::quit => CmdResult::Quit, Internal::root_down => { let tree = self.displayed_tree(); if tree.selection > 0 { let root_len = tree.root().components().count(); let new_root = tree.selected_line().path .components() .take(root_len + 1) .collect(); self.modified( screen, new_root, tree.options.clone(), None, bang, con, ) } else { CmdResult::error("No selected line") } } Internal::root_up => { let tree = self.displayed_tree(); let root = tree.root(); if let Some(new_root) = root.parent() { self.modified( screen, new_root.to_path_buf(), tree.options.clone(), None, bang, con, ) } else { CmdResult::error(format!("{root:?} has no parent")) } } Internal::search_again => { match self.filtered_tree.as_ref().map(|t| t.total_search) { None => { // we delegate to the app the task of looking for a preview pattern // used before this state CmdResult::HandleInApp(Internal::search_again) } Some(true) => { CmdResult::error("search was already total: all possible matches have been ranked") } Some(false) => { self.search(self.displayed_tree().options.pattern.clone(), true); CmdResult::Keep } } } Internal::select => internal_select::on_internal( internal_exec, input_invocation, trigger_type, self.displayed_tree_mut(), app_state, cc, ), Internal::select_first => { self.displayed_tree_mut().try_select_first(); CmdResult::Keep } Internal::select_last => { let page_height = BrowserState::page_height(screen); self.displayed_tree_mut().try_select_last(page_height); CmdResult::Keep } Internal::show => { let path = internal_path::determine_path( internal_exec, input_invocation, trigger_type, self.displayed_tree(), app_state, cc, ); match path { Some(path) => { let res = self.displayed_tree_mut().show_path(&path, con); match res { Ok(()) => { let page_height = BrowserState::page_height(screen); self.displayed_tree_mut().make_selection_visible(page_height); CmdResult::Keep } Err(e) => CmdResult::DisplayError(format!("{e}")), } } None => { CmdResult::Keep } } } Internal::stage_all_directories => { let pattern = self.displayed_tree().options.pattern.clone(); let file_type_condition = FileTypeCondition::Directory; self.pending_task = Some(BrowserTask::StageAll{pattern, file_type_condition}); if cc.app.stage_panel.is_none() { let stage_options = self.tree.options.without_pattern(); CmdResult::NewPanel { state: Box::new(StageState::new(app_state, stage_options, con)), purpose: PanelPurpose::None, direction: HDir::Right, } } else { CmdResult::Keep } } Internal::stage_all_files => { let pattern = self.displayed_tree().options.pattern.clone(); let file_type_condition = FileTypeCondition::File; self.pending_task = Some(BrowserTask::StageAll{pattern, file_type_condition}); if cc.app.stage_panel.is_none() { let stage_options = self.tree.options.without_pattern(); CmdResult::NewPanel { state: Box::new(StageState::new(app_state, stage_options, con)), purpose: PanelPurpose::None, direction: HDir::Right, } } else { CmdResult::Keep } } Internal::start_end_panel => { if cc.panel.purpose.is_arg_edition() { debug!("start_end understood as end"); CmdResult::ClosePanel { validate_purpose: true, panel_ref: PanelReference::Active, clear_cache: false, } } else { debug!("start_end understood as start"); let tree_options = self.displayed_tree().options.clone(); if let Some(input_invocation) = input_invocation { // we'll go for input arg editing let path = if let Some(input_arg) = &input_invocation.args { path::path_from(self.root(), PathAnchor::Unspecified, input_arg) } else { self.root().to_path_buf() }; let arg_type = SelectionType::Any; // We might do better later let purpose = PanelPurpose::ArgEdition { arg_type }; internal_focus::new_panel_on_path( path, screen, tree_options, purpose, con, HDir::Right, ) } else { // we just open a new panel on the selected path, // without purpose internal_focus::new_panel_on_path( self.displayed_tree().selected_line().path.to_path_buf(), screen, tree_options, PanelPurpose::None, con, HDir::Right, ) } } } Internal::total_search => { match self.filtered_tree.as_ref().map(|t| t.total_search) { None => { CmdResult::error("this verb can be used only after a search") } Some(true) => { CmdResult::error("search was already total: all possible matches have been ranked") } Some(false) => { self.search(self.displayed_tree().options.pattern.clone(), true); CmdResult::Keep } } } Internal::trash => { let path = self.displayed_tree().selected_line().path.to_path_buf(); info!("trash {:?}", &path); #[cfg(feature = "trash")] match trash::delete(&path) { Ok(()) => CmdResult::RefreshState { clear_cache: true }, Err(e) => { warn!("trash error: {:?}", &e); CmdResult::DisplayError(format!("trash error: {:?}", &e)) } } #[cfg(not(feature = "trash"))] CmdResult::DisplayError("feature not enabled or platform does not support trash".into()) } Internal::up_tree => match self.displayed_tree().root().parent() { Some(path) => internal_focus::on_path( path.to_path_buf(), screen, self.displayed_tree().options.clone(), bang, con, ), None => CmdResult::error("no parent found"), }, _ => self.on_internal_generic( w, invocation_parser, internal_exec, input_invocation, trigger_type, app_state, cc, )?, }) } fn no_verb_status( &self, has_previous_state: bool, con: &AppContext, width: usize, ) -> Status { let tree = self.displayed_tree(); if tree.is_empty() && tree.build_report.hidden_count > 0 { let mut parts = Vec::new(); if let Some(md) = con.standard_status.all_files_hidden.clone() { parts.push(md); } if let Some(md) = con.standard_status.all_files_ignored.clone() { parts.push(md); } if !parts.is_empty() { return Status::from_error(parts.join(". ")); } } let mut ssb = con.standard_status.builder( PanelStateType::Tree, tree.selected_line().as_selection(), width, ); ssb.has_previous_state = has_previous_state; ssb.is_filtered = self.filtered_tree.is_some(); ssb.has_removed_pattern = false; ssb.on_tree_root = tree.selection == 0; ssb.status() } /// do some work, totally or partially, if there's some to do. /// Stop as soon as the dam asks for interruption fn do_pending_task( &mut self, app_state: &mut AppState, screen: Screen, con: &AppContext, dam: &mut Dam, ) -> Result<(), ProgramError> { if let Some(pending_task) = self.pending_task.take() { match pending_task { BrowserTask::Search { pattern, total } => { let pattern_str = pattern.raw.clone(); let mut options = self.tree.options.clone(); options.pattern = pattern; let root = self.tree.root().clone(); let page_height = BrowserState::page_height(screen); let builder = TreeBuilder::from(root, options, page_height, con)?; let filtered_tree = time!( Info, "tree filtering", &pattern_str, builder.build_tree(total, dam), ); if let Ok(mut ft) = filtered_tree { ft.try_select_best_match(); ft.make_selection_visible(BrowserState::page_height(screen)); self.filtered_tree = Some(ft); } } BrowserTask::StageAll { pattern, file_type_condition } => { debug!("stage all pattern: {:?}", pattern); let tree = self.displayed_tree(); let root = tree.root().clone(); let mut options = tree.options.clone(); let total_search = true; options.pattern = pattern; // should be the same let builder = TreeBuilder::from(root, options, con.max_staged_count, con); let mut paths = builder .and_then(|mut builder| { builder.matches_max = Some(con.max_staged_count); time!(builder.build_paths( total_search, dam, |line| { debug!("??staging {:?}", &line.path); file_type_condition.accepts_path(&line.path) } )) })?; for path in paths.drain(..) { app_state.stage.add(path); } } } } else if self.displayed_tree().is_missing_git_status_computation() { let root_path = self.displayed_tree().root(); let git_status = git::get_tree_status(root_path, dam); self.displayed_tree_mut().git_status = git_status; } else { self.displayed_tree_mut().fetch_some_missing_dir_sum(dam, con); } Ok(()) } fn display( &mut self, w: &mut W, disc: &DisplayContext, ) -> Result<(), ProgramError> { let dp = DisplayableTree { app_state: Some(disc.app_state), tree: self.displayed_tree(), skin: &disc.panel_skin.styles, ext_colors: &disc.con.ext_colors, area: disc.state_area.clone(), in_app: true, }; dp.write_on(w) } fn refresh(&mut self, screen: Screen, con: &AppContext) -> Command { let page_height = BrowserState::page_height(screen); // refresh the base tree if let Err(e) = self.tree.refresh(page_height, con) { warn!("refreshing base tree failed : {:?}", e); } // refresh the filtered tree, if any Command::from_pattern(match self.filtered_tree { Some(ref mut tree) => { if let Err(e) = tree.refresh(page_height, con) { warn!("refreshing filtered tree failed : {:?}", e); } &tree.options.pattern } None => &self.tree.options.pattern, }) } fn get_flags(&self) -> Vec { let options = &self.displayed_tree().options; vec![ Flag { name: "h", value: if options.show_hidden { "y" } else { "n" }, }, Flag { name: "gi", value: if options.respect_git_ignore { "y" } else { "n" }, }, ] } fn get_starting_input(&self) -> String { if let Some(BrowserTask::Search { pattern, .. }) = self.pending_task.as_ref() { pattern.raw.clone() } else { self.displayed_tree().options.pattern.raw.clone() } } } broot-1.46.3/src/browser/mod.rs000064400000000000000000000000711046102023000144410ustar 00000000000000mod browser_state; pub use browser_state::BrowserState; broot-1.46.3/src/cli/args.rs000064400000000000000000000140311046102023000137030ustar 00000000000000// Warning: this module can't import broot's stuf due to its use in build.rs use { clap::{Parser, ValueEnum}, std::{ path::PathBuf, str::FromStr, }, }; /// Launch arguments #[derive(Debug, Parser)] #[command(about, version, disable_version_flag = true, disable_help_flag = true)] pub struct Args { /// Print help information #[arg(long)] pub help: bool, /// print the version #[arg(long)] pub version: bool, /// Semicolon separated paths to specific config files #[arg(long, value_name = "paths")] pub conf: Option, /// Show the last modified date of files and directories #[arg(short, long)] pub dates: bool, /// Don't show the last modified date #[arg(short='D', long)] pub no_dates: bool, #[arg(short='f', long)] /// Only show folders pub only_folders: bool, /// Show folders and files alike #[arg(short='F', long)] pub no_only_folders: bool, /// Show filesystem info on top #[arg(long)] pub show_root_fs: bool, /// Only show trees up to a certain depth #[arg(long)] pub max_depth: Option, /// Show git statuses on files and stats on repo #[arg(short='g', long)] pub show_git_info: bool, /// Don't show git statuses on files and stats on repo #[arg(short='G', long)] pub no_show_git_info: bool, #[arg(long)] /// Only show files having an interesting git status, including hidden ones pub git_status: bool, #[arg(short='h', long)] /// Show hidden files pub hidden: bool, #[arg(short='H', long)] /// Don't show hidden files pub no_hidden: bool, #[arg(short='i', long)] /// Show git ignored files pub git_ignored: bool, #[arg(short='I', long)] /// Don't show git ignored files pub no_git_ignored: bool, #[arg(short='p', long)] /// Show permissions pub permissions: bool, #[arg(short='P', long)] /// Don't show permissions pub no_permissions: bool, #[arg(short='s', long)] /// Show the size of files and directories pub sizes: bool, #[arg(short='S', long)] /// Don't show sizes pub no_sizes: bool, #[arg(long)] /// Sort by count (only show one level of the tree) pub sort_by_count: bool, #[arg(long)] /// Sort by date (only show one level of the tree) pub sort_by_date: bool, #[arg(long)] /// Sort by size (only show one level of the tree) pub sort_by_size: bool, #[arg(long)] /// Same as sort-by-type-dirs-first pub sort_by_type: bool, #[arg(long)] /// Do not show the tree, even if allowed by sorting mode. pub no_tree: bool, #[arg(long)] /// Show the tree, when allowed by sorting mode. pub tree: bool, #[arg(long)] /// Sort by type, directories first (only show one level of the tree) pub sort_by_type_dirs_first: bool, #[arg(long)] /// Sort by type, directories last (only show one level of the tree) pub sort_by_type_dirs_last: bool, /// Don't sort #[arg(long)] pub no_sort: bool, /// Sort by size, show ignored and hidden files #[arg(short, long)] pub whale_spotting: bool, /// No sort, no show hidden, no show git ignored #[arg(short='W', long)] pub no_whale_spotting: bool, /// Trim the root too and don't show a scrollbar #[arg(short='t', long)] pub trim_root: bool, /// Don't trim the root level, show a scrollbar #[arg(short='T', long)] pub no_trim_root: bool, /// Where to write the produced cmd (if any) #[arg(long, value_name = "path")] pub outcmd: Option, /// Optional path for verbs using `:write_output` #[arg(long, value_name = "verb-output")] pub verb_output: Option, /// Semicolon separated commands to execute #[arg(short, long, value_name = "cmd")] pub cmd: Option, /// Whether to have styles and colors #[arg(long, default_value="auto", value_name = "color")] pub color: TriBool, /// Height (if you don't want to fill the screen or for file export) #[arg(long, value_name = "height")] pub height: Option, /// Install or reinstall the br shell function #[arg(long)] pub install: bool, /// Manually set installation state #[arg(long, value_name = "state")] pub set_install_state: Option, /// Print to stdout the br function for a given shell #[arg(long, value_name = "shell")] pub print_shell_function: Option, /// A socket to listen to for commands #[cfg(unix)] #[arg(long, value_name = "socket")] pub listen: Option, /// Ask for the current root of the remote broot #[cfg(unix)] #[arg(long)] pub get_root: bool, /// Write default conf files in given directory #[arg(long, value_name = "path")] pub write_default_conf: Option, /// A socket to send commands to #[cfg(unix)] #[arg(long, value_name = "socket")] pub send: Option, /// Root Directory pub root: Option, } /// This is an Option but I didn't find any way to configure /// clap to parse an Option as I want #[derive(ValueEnum)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum TriBool { Auto, Yes, No, } impl TriBool { pub fn unwrap_or_else(self, f: F) -> bool where F: FnOnce() -> bool { match self { Self::Auto => f(), Self::Yes => true, Self::No => false, } } } #[derive(Debug, Clone, Copy, clap::ValueEnum)] pub enum CliShellInstallState { Undefined, // before any install, this is the initial state Refused, Installed, } impl FromStr for CliShellInstallState { type Err = String; fn from_str(state: &str) -> Result { match state { "undefined" => Ok(Self::Undefined), "refused" => Ok(Self::Refused), "installed" => Ok(Self::Installed), _ => Err( format!("unexpected install state: {state:?}") ), } } } broot-1.46.3/src/cli/install_launch_args.rs000064400000000000000000000024471046102023000167730ustar 00000000000000use { crate::{ errors::ProgramError, cli::{Args, CliShellInstallState}, }, std::{ env, }, }; /// launch arguments related to installation /// (not used by the application after the first step) pub struct InstallLaunchArgs { pub install: Option, // installation is required pub set_install_state: Option, // the state to set pub print_shell_function: Option, // shell function to print on stdout } impl InstallLaunchArgs { pub fn from(args: &Args) -> Result { let mut install = None; if let Ok(s) = env::var("BR_INSTALL") { if s == "yes" { install = Some(true); } else if s == "no" { install = Some(false); } else { warn!("Unexpected value of BR_INSTALL: {:?}", s); } } // the cli arguments may override the env var value if args.install { install = Some(true); } let print_shell_function = args.print_shell_function.clone(); let set_install_state = args.set_install_state; Ok(Self { install, set_install_state, print_shell_function, }) } } broot-1.46.3/src/cli/mod.rs000064400000000000000000000130661046102023000135350ustar 00000000000000//! this module manages reading and translating //! the arguments passed on launch of the application. mod args; mod install_launch_args; pub use { args::*, install_launch_args::*, }; use { crate::{ app::{App, AppContext}, conf::{Conf, write_default_conf_in}, display, errors::ProgramError, launchable::Launchable, shell_install::{ShellInstall, ShellInstallState}, verb::VerbStore, }, clap::{CommandFactory, Parser}, clap_help::Printer, crokey::crossterm::{ cursor, event::{DisableMouseCapture, EnableMouseCapture}, terminal::{EnterAlternateScreen, LeaveAlternateScreen}, QueueableCommand, }, std::{ io::{self, Write}, path::PathBuf, }, }; static INTRO: &str = " **broot** lets you explore file hierarchies with a tree-like view, manipulate and preview files, launch actions, and define your own shortcuts. **broot** is best launched as `br`: this shell function gives you access to more commands, especially `cd.` The br shell function is interactively installed on first broot launch. Flags and options can be classically passed on launch but also written in the configuration file. Each flag has a counter-flag so that you can cancel at command line a flag which has been set in the configuration file. Complete documentation and tips at https://dystroy.org/broot "; /// run the application, and maybe return a launchable /// which must be run after broot pub fn run() -> Result, ProgramError> { // parse the launch arguments we got from cli let args = Args::parse(); let mut must_quit = false; if args.help { Printer::new(Args::command()) .with_max_width(110) .with("introduction", INTRO) .with("options", clap_help::TEMPLATE_OPTIONS_MERGED_VALUE) .without("author") .print_help(); must_quit = true; } if args.version { println!("broot {}", env!("CARGO_PKG_VERSION")); must_quit = true; } if let Some(dir) = &args.write_default_conf { write_default_conf_in(dir)?; must_quit = true; } // read the install related arguments let install_args = InstallLaunchArgs::from(&args)?; let mut shell_install = ShellInstall::new(install_args.install == Some(true)); // execute installation things required by launch args if let Some(state) = install_args.set_install_state { let state: ShellInstallState = state.into(); state.write(&shell_install)?; must_quit = true; } if let Some(shell) = &install_args.print_shell_function { ShellInstall::print(shell)?; must_quit = true; } if must_quit { return Ok(None); } // read the list of specific config files let specific_conf: Option> = args.conf .as_ref() .map(|s| s.split(';').map(PathBuf::from).collect()); // if we don't run on a specific config file, we check the // configuration if specific_conf.is_none() && install_args.install != Some(false) { // TODO clean the next few lines when inspect_err is stable let res = shell_install.check(); if let Err(e) = &res { shell_install.comment_error(e); } res?; if shell_install.should_quit { return Ok(None); } } // read the configuration file(s): either the standard one // or the ones required by the launch args let mut config = match &specific_conf { Some(conf_paths) => { let mut conf = Conf::default(); for path in conf_paths { conf.read_file(path.to_path_buf())?; } conf } _ => time!(Conf::from_default_location())?, }; debug!("config: {:#?}", &config); // verb store is completed from the config file(s) let verb_store = VerbStore::new(&mut config)?; let mut context = AppContext::from(args, verb_store, &config)?; #[cfg(unix)] if let Some(server_name) = &context.launch_args.send { use crate::{ command::Sequence, net::{Client, Message}, }; let client = Client::new(server_name); if let Some(seq) = &context.launch_args.cmd { let message = Message::Sequence(Sequence::new_local(seq.to_string())); client.send(&message)?; } else if !context.launch_args.get_root { let message = Message::Command( format!(":focus {}", context.initial_root.to_string_lossy()) ); client.send(&message)?; }; if context.launch_args.get_root { client.send(&Message::GetRoot)?; } return Ok(None); } let mut w = display::writer(); let app = App::new(&context)?; w.queue(EnterAlternateScreen)?; w.queue(cursor::Hide)?; if context.capture_mouse { w.queue(EnableMouseCapture)?; } let r = app.run(&mut w, &mut context, &config); w.flush()?; if context.capture_mouse { w.queue(DisableMouseCapture)?; } w.queue(cursor::Show)?; w.queue(LeaveAlternateScreen)?; w.flush()?; clear_resources(); r } fn clear_resources() { info!("clearing resources"); crate::kitty::manager().lock().unwrap().delete_temp_files(); } /// wait for user input, return `true` if they didn't answer 'n' pub fn ask_authorization() -> io::Result { let mut answer = String::new(); io::stdin().read_line(&mut answer)?; let answer = answer.trim(); Ok(!matches!(answer, "n" | "N")) } broot-1.46.3/src/command/command.rs000064400000000000000000000065671046102023000152530ustar 00000000000000use { super::*, crate::{ pattern::*, verb::{Internal, VerbInvocation, VerbId}, }, bet::BeTree, }; /// a command which may result in a change in the application state. /// /// It may come from a shortcut, from the parsed input, from an argument /// given on launch. #[derive(Debug, Clone)] pub enum Command { /// no command None, /// a verb invocation, unfinished /// (user didn't hit enter) VerbEdit(VerbInvocation), /// verb invocation, finished /// (coming from --cmd, or after the user hit enter) VerbInvocate(VerbInvocation), /// call of an internal done without the input /// (using a trigger key for example) Internal { internal: Internal, input_invocation: Option, }, /// call of a verb done without the input /// (using a trigger key for example) VerbTrigger { verb_id: VerbId, input_invocation: Option, }, /// a pattern being edited PatternEdit { raw: String, expr: BeTree, }, /// a mouse click Click(u16, u16), /// a mouse double-click /// Always come after a simple click at same position DoubleClick(u16, u16), } impl Command { pub fn empty() -> Command { Command::None } pub fn is_none(&self) -> bool { matches!(self, Command::None) } pub fn as_verb_invocation(&self) -> Option<&VerbInvocation> { match self { Self::VerbEdit(vi) => Some(vi), Self::VerbInvocate(vi) => Some(vi), Self::Internal { input_invocation, .. } => input_invocation.as_ref(), Self::VerbTrigger { input_invocation, .. } => input_invocation.as_ref(), _ => None, } } /// build a command from the parsed string representation /// /// The command being finished is the difference between /// a command being edited and a command launched (which /// happens on enter in the input). pub fn from_parts(mut cp: CommandParts, finished: bool) -> Self { if let Some(verb_invocation) = cp.verb_invocation.take() { if finished { Self::VerbInvocate(verb_invocation) } else { Self::VerbEdit(verb_invocation) } } else if finished { Self::Internal { internal: Internal::open_stay, input_invocation: None, } } else { Self::PatternEdit { raw: cp.raw_pattern, expr: cp.pattern, } } } /// tells whether this action is a verb being invocated on enter /// in the input field pub fn is_verb_invocated_from_input(&self) -> bool { matches!(self, Self::VerbInvocate(_)) } /// create a command from a raw input. /// /// `finished` makes the command an executed form, /// it's equivalent to using the Enter key in the Gui. pub fn from_raw(raw: String, finished: bool) -> Self { let parts = CommandParts::from(raw); Self::from_parts(parts, finished) } /// build a non executed command from a pattern pub fn from_pattern(pattern: &InputPattern) -> Self { Command::from_raw(pattern.raw.clone(), false) } } impl Default for Command { fn default() -> Command { Command::empty() } } broot-1.46.3/src/command/completion.rs000064400000000000000000000210321046102023000157660ustar 00000000000000use { super::CommandParts, crate::{ app::{ AppContext, PanelStateType, SelInfo, }, path::{self, PathAnchor}, syntactic::SYNTAX_THEMES, verb::{ ArgDef, PrefixSearchResult, }, }, lazy_regex::regex_captures, std::{ io, path::Path, }, }; /// find the longest common start of a and b fn common_start<'l>(a: &'l str, b: &str) -> &'l str { let l = a.len().min(b.len()); for i in 0..l { if a.as_bytes()[i] != b.as_bytes()[i] { return &a[..i]; } } &a[..l] } /// how an input can be completed #[derive(Debug)] pub enum Completions { /// no completion found None, /// all possible completions have this common root Common(String), /// a list of possible completions List(Vec), } impl Completions { fn from_list(completions: Vec) -> Self { if completions.is_empty() { return Self::None; } let mut iter = completions.iter(); let mut common: &str = match iter.next() { Some(s) => s, _ => { return Self::None; } }; for c in iter { common = common_start(common, c); } if common.is_empty() { Self::List(completions) } else { Self::Common(common.to_string()) } } /// the wholes are assumed to all start with start. fn for_wholes( start: &str, wholes: Vec<&str>, ) -> Self { let completions = wholes.iter() .map(|w| if let Some(stripped) = w.strip_prefix(start) { stripped } else { // this might become a feature but right now it's a bug warn!("unexpected non completing whole: {:?}", w); *w } ) .map(|c| c.to_string()) .collect(); Self::from_list(completions) } fn for_verb( start: &str, con: &AppContext, sel_info: SelInfo<'_>, panel_state_type: Option, ) -> Self { match con.verb_store.search(start, Some(sel_info), false, panel_state_type) { PrefixSearchResult::NoMatch => Self::None, PrefixSearchResult::Match(name, _) => { if start.len() >= name.len() { debug_assert!(name == start); Self::None } else { Self::Common(name[start.len()..].to_string()) } } PrefixSearchResult::Matches(completions) => Self::for_wholes( start, completions, ), } } fn list_for_path( verb_name: &str, arg: &str, path: &Path, sel_info: SelInfo<'_>, con: &AppContext, panel_state_type: Option, ) -> io::Result> { let anchor = match con.verb_store.search(verb_name, Some(sel_info), false, panel_state_type) { PrefixSearchResult::Match(_, verb) => verb.get_unique_arg_anchor(), _ => PathAnchor::Unspecified, }; let (_, parent_part, child_part) = regex_captures!(r"^(.*?)([^/]*)$", arg).unwrap(); let parent = path::path_from(path, anchor, parent_part); let mut children = Vec::new(); if !parent.exists() { debug!("no path completion possible because {:?} doesn't exist", &parent); } else { for entry in parent.read_dir()? { let entry = entry?; let mut name = entry.file_name().to_string_lossy().to_string(); if !child_part.is_empty() { if !name.starts_with(child_part) { continue; } if name == child_part && entry.file_type()?.is_dir() { name = "/".to_string(); } else { name.drain(0..child_part.len()); } } children.push(name); } } Ok(children) } /// we have a verb, we try to complete one of the args fn for_arg( verb_name: &str, arg: &str, con: &AppContext, sel_info: SelInfo<'_>, panel_state_type: Option, ) -> Self { if arg.contains(' ') { return Self::None; } // we try to get the type of argument let arg_def = con .verb_store .search_sel_info_unique(verb_name, sel_info, panel_state_type) .and_then(|verb| verb.invocation_parser.as_ref()) .and_then(|invocation_parser| invocation_parser.get_unique_arg_def()); if matches!(arg_def, Some(ArgDef::Theme)) { Self::for_theme_arg(arg) } else { Self::for_path_arg(verb_name, arg, con, sel_info, panel_state_type) } } /// we have a verb and it asks for a theme fn for_theme_arg( arg: &str, ) -> Self { let arg = arg.to_lowercase(); let completions: Vec = SYNTAX_THEMES .iter() .map(|st| st.name().to_lowercase()) .filter_map(|name| name.strip_prefix(&arg).map(|s| s.to_string())) .collect(); Self::from_list(completions) } /// we have a verb and it asks for a path fn for_path_arg( verb_name: &str, arg: &str, con: &AppContext, sel_info: SelInfo<'_>, panel_state_type: Option, ) -> Self { // in the future we might offer completion of other types // of arguments, maybe user supplied, but there's no use case // now so we'll just assume the user wants to complete a path. if arg.contains(' ') { return Self::None; } match &sel_info { SelInfo::None => Self::None, SelInfo::One(sel) => { match Self::list_for_path(verb_name, arg, sel.path, sel_info, con, panel_state_type) { Ok(list) => Self::from_list(list), Err(e) => { warn!("Error while trying to complete path: {:?}", e); Self::None } } } SelInfo::More(stage) => { // We're looking for the possible completions which // are valid for all elements of the stage let mut lists = stage.paths() .iter() .filter_map(|path| { Self::list_for_path( verb_name, arg, path, sel_info, con, panel_state_type, ).ok() }); let mut list = match lists.next() { Some(list) => list, None => { // can happen if there were IO errors on paths in stage, for example // on removals return Self::None; } }; for ol in lists { list.retain(|c| ol.contains(c)); if list.is_empty() { break; } } Self::from_list(list) } } } pub fn for_input( parts: &CommandParts, con: &AppContext, sel_info: SelInfo<'_>, panel_state_type: Option, ) -> Self { match &parts.verb_invocation { Some(invocation) if !invocation.is_empty() => { match &invocation.args { None => { // looking into verb completion Self::for_verb(&invocation.name, con, sel_info, panel_state_type) } Some(args) if !args.is_empty() => { // looking into arg completion Self::for_arg(&invocation.name, args, con, sel_info, panel_state_type) } _ => { // nothing possible Self::None } } } _ => Self::None, // no possible completion if no verb invocation } } } broot-1.46.3/src/command/mod.rs000064400000000000000000000005121046102023000143740ustar 00000000000000mod command; mod completion; mod panel_input; mod parts; mod sequence; mod sel; mod scroll; mod trigger_type; pub use { command::Command, completion::Completions, panel_input::PanelInput, parts::CommandParts, sequence::Sequence, sel::move_sel, scroll::ScrollCommand, trigger_type::TriggerType, }; broot-1.46.3/src/command/panel_input.rs000064400000000000000000000410111046102023000161320ustar 00000000000000use { super::*, crate::{ app::*, display::W, errors::ProgramError, keys, skin::PanelSkin, verb::*, }, crokey::{key, KeyCombination}, crokey::crossterm::{ cursor, event::{ Event, KeyModifiers, MouseButton, MouseEvent, MouseEventKind, }, queue, }, termimad::{Area, TimedEvent, InputField}, }; /// Wrap the input of a panel, receive events and make commands pub struct PanelInput { pub input_field: InputField, tab_cycle_count: usize, input_before_cycle: Option, } impl PanelInput { pub fn new(area: Area) -> Self { Self { input_field: InputField::new(area), tab_cycle_count: 0, input_before_cycle: None, } } pub fn set_content(&mut self, content: &str) { self.input_field.set_str(content); } pub fn get_content(&self) -> String { self.input_field.get_content() } pub fn display( &mut self, w: &mut W, active: bool, mode: Mode, mut area: Area, panel_skin: &PanelSkin, ) -> Result, ProgramError> { self.input_field.set_normal_style(panel_skin.styles.input.clone()); self.input_field.set_focus(active && mode == Mode::Input); if mode == Mode::Command && active { queue!(w, cursor::MoveTo(area.left, area.top))?; panel_skin.styles.mode_command_mark.queue_str(w, "C")?; area.width -= 1; area.left += 1; } self.input_field.set_area(area); let cursor_pos = self.input_field.display_on(w)?; Ok(cursor_pos) } /// consume the event to /// - maybe change the input /// - build a command /// then redraw the input field #[allow(clippy::too_many_arguments)] pub fn on_event( &mut self, w: &mut W, timed_event: TimedEvent, con: &AppContext, sel_info: SelInfo<'_>, app_state: &AppState, mode: Mode, panel_state_type: PanelStateType, ) -> Result { let cmd = match timed_event { TimedEvent { event: Event::Mouse(MouseEvent { kind, column, row, modifiers: KeyModifiers::NONE }), .. } => { self.on_mouse(timed_event, kind, column, row) } TimedEvent { key_combination: Some(key), .. } => { self.on_key(timed_event, key, con, sel_info, app_state, mode, panel_state_type) } _ => Command::None, }; self.input_field.display_on(w)?; Ok(cmd) } /// check whether the verb is an action on the input (like /// deleting a word) and if it's the case, applies it and /// return true fn handle_input_related_verb( &mut self, verb: &Verb, _con: &AppContext, ) -> bool { if let VerbExecution::Internal(internal_exec) = &verb.execution { self.handle_input_related_internal(internal_exec.internal) } else { false } } /// Supporting direct calls of internals not on key events (eg from a verb /// having a cmd, or from a --cmd), update the input and build a command /// if the internal is an action on the input (like deleting a word) pub fn on_internal( &mut self, internal: Internal, ) -> Command { if self.handle_input_related_internal(internal) { Command::from_raw(self.input_field.get_content(), false) } else { Command::None } } /// check whether the internal is an action on the input (like /// deleting a word) and if it's the case, applies it and /// return true fn handle_input_related_internal( &mut self, internal: Internal, ) -> bool { match internal { Internal::input_clear => { if self.input_field.get_content().is_empty() { false } else { self.input_field.clear(); true } } Internal::input_del_char_left => self.input_field.del_char_left(), Internal::input_del_char_below => self.input_field.del_char_below(), Internal::input_del_word_left => self.input_field.del_word_left(), Internal::input_del_word_right => self.input_field.del_word_right(), Internal::input_go_left => self.input_field.move_left(), Internal::input_go_right => self.input_field.move_right(), Internal::input_go_word_left => self.input_field.move_word_left(), Internal::input_go_word_right => self.input_field.move_word_right(), Internal::input_go_to_start => self.input_field.move_to_start(), Internal::input_go_to_end => self.input_field.move_to_end(), #[cfg(feature = "clipboard")] Internal::input_selection_cut => { let s = self.input_field.cut_selection(); if let Err(err) = terminal_clipboard::set_string(s) { warn!("error in writing into clipboard: {}", err); } true } #[cfg(feature = "clipboard")] Internal::input_selection_copy => { let s = self.input_field.copy_selection(); if let Err(err) = terminal_clipboard::set_string(s) { warn!("error in writing into clipboard: {}", err); } true } #[cfg(feature = "clipboard")] Internal::input_paste => { match terminal_clipboard::get_string() { Ok(pasted) => { for c in pasted .chars() .filter(|c| c.is_alphanumeric() || c.is_ascii_punctuation()) { self.input_field.put_char(c); } } Err(e) => { warn!("Error in reading clipboard: {:?}", e); } } true } _ => false, } } /// when a key is used to enter input mode, we don't always /// consume it. Sometimes it should be consumed, sometimes it /// should be added to the input fn enter_input_mode_with_key( &mut self, key: KeyCombination, parts: &CommandParts, ) { if let Some(c) = key.as_letter() { let add = match c { // '/' if !parts.raw_pattern.is_empty() => true, ' ' if parts.verb_invocation.is_none() => true, ':' if parts.verb_invocation.is_none() => true, _ => false, }; if add { self.input_field.put_char(c); } } } /// escape (bound to the 'esc' key) /// /// This function is better called from the on_key method of /// panel input, when a key triggers it, because then it /// can also properly deal with completion sequence. /// When ':escape' is called from a verb's cmd sequence, then /// it's not called on on_key but by the app. pub fn escape( &mut self, con: &AppContext, mode: Mode, ) -> Command { self.tab_cycle_count = 0; if let Some(raw) = self.input_before_cycle.take() { // we cancel the tab cycling self.input_field.set_str(&raw); self.input_before_cycle = None; Command::from_raw(raw, false) } else if con.modal && mode == Mode::Input { // leave insertion mode Command::Internal { internal: Internal::mode_command, input_invocation: None, } } else { // general back command let raw = self.input_field.get_content(); let parts = CommandParts::from(raw.clone()); self.input_field.clear(); let internal = Internal::back; Command::Internal { internal, input_invocation: parts.verb_invocation, } } } /// autocomplete a verb (bound to 'tab') fn auto_complete_verb( &mut self, con: &AppContext, sel_info: SelInfo<'_>, raw: String, parts: CommandParts, panel_state_type: Option, ) -> Command { let parts_before_cycle; let completable_parts = if let Some(s) = &self.input_before_cycle { parts_before_cycle = CommandParts::from(s.clone()); &parts_before_cycle } else { &parts }; let completions = Completions::for_input(completable_parts, con, sel_info, panel_state_type); let added = match completions { Completions::None => { debug!("nothing to complete!"); self.tab_cycle_count = 0; self.input_before_cycle = None; None } Completions::Common(completion) => { self.tab_cycle_count = 0; Some(completion) } Completions::List(mut completions) => { let idx = self.tab_cycle_count % completions.len(); if self.tab_cycle_count == 0 { self.input_before_cycle = Some(raw.to_string()); } self.tab_cycle_count += 1; Some(completions.swap_remove(idx)) } }; if let Some(added) = added { let mut raw = self .input_before_cycle .as_ref() .map_or(raw, |s| s.to_string()); raw.push_str(&added); self.input_field.set_str(&raw); Command::from_raw(raw, false) } else { Command::None } } fn find_key_verb<'c>( &mut self, key: KeyCombination, con: &'c AppContext, sel_info: SelInfo<'_>, panel_state_type: PanelStateType, ) -> Option<&'c Verb> { for verb in con.verb_store.verbs() { // note that there can be several verbs with the same key and // not all of them can apply if !verb.keys.contains(&key) { continue; } if !sel_info.is_accepted_by(verb.selection_condition) { continue; } if !verb.can_be_called_in_panel(panel_state_type) { continue; } if !verb.file_extensions.is_empty() { let extension = sel_info.extension(); if !extension.is_some_and(|ext| verb.file_extensions.iter().any(|ve| ve == ext)) { continue; } } debug!("verb for key: {}", &verb.execution); return Some(verb); } None } /// Consume the event, maybe change the input, return a command fn on_mouse( &mut self, timed_event: TimedEvent, kind: MouseEventKind, column: u16, row: u16, ) -> Command { if self.input_field.apply_timed_event(&timed_event) { Command::empty() } else { match kind { MouseEventKind::Up(MouseButton::Left) => { if timed_event.double_click { Command::DoubleClick(column, row) } else { Command::Click(column, row) } } MouseEventKind::ScrollDown => { Command::Internal { internal: Internal::line_down, input_invocation: None, } } MouseEventKind::ScrollUp => { Command::Internal { internal: Internal::line_up, input_invocation: None, } } _ => Command::None, } } } fn is_key_allowed_for_verb( &self, key: KeyCombination, mode: Mode, ) -> bool { match mode { Mode::Input => match key { key!(left) => !self.input_field.can_move_left(), key!(right) => !self.input_field.can_move_right(), _ => !keys::is_key_only_modal(key), } Mode::Command => true, } } /// Consume the event, maybe change the input, return a command #[allow(clippy::too_many_arguments)] fn on_key( &mut self, timed_event: TimedEvent, key: KeyCombination, con: &AppContext, sel_info: SelInfo<'_>, app_state: &AppState, mode: Mode, panel_state_type: PanelStateType, ) -> Command { // value of raw and parts before any key related change let raw = self.input_field.get_content(); let parts = CommandParts::from(raw.clone()); let verb = if self.is_key_allowed_for_verb(key, mode) { self.find_key_verb( key, con, sel_info, panel_state_type, ) } else { None }; // WARNINGS: // - beware the execution order below: we must execute // escape before the else clause of next_match, and we must // be sure this else clause (which ends cycling) is always // executed when neither next_match or escape is triggered // - some behaviors can't really be handled as normally // triggered internals because of the interactions with // the input // usually 'esc' key if Verb::is_some_internal(verb, Internal::escape) { return self.escape(con, mode); } // 'tab' completion of a verb or one of its arguments if Verb::is_some_internal(verb, Internal::next_match) { if parts.verb_invocation.is_some() { return self.auto_complete_verb(con, sel_info, raw, parts, Some(panel_state_type)); } // if no verb is being edited, the state may handle this internal // in a specific way } else { self.tab_cycle_count = 0; self.input_before_cycle = None; } // 'enter': trigger the verb if any on the input. If none, then may be // used as trigger of another verb if key == key!(enter) && parts.has_not_empty_verb_invocation() { return Command::from_parts(parts, true); } // a '?' opens the help when it's the first char or when it's part // of the verb invocation. It may be used as a verb name in other cases if (key == key!('?') || key == key!(shift-'?')) && (raw.is_empty() || parts.verb_invocation.is_some()) { return Command::Internal { internal: Internal::help, input_invocation: parts.verb_invocation, }; } if let Some(verb) = verb { if self.handle_input_related_verb(verb, con) { return Command::from_raw(self.input_field.get_content(), false); } if mode != Mode::Input && verb.is_internal(Internal::mode_input) { self.enter_input_mode_with_key(key, &parts); } if verb.auto_exec { return Command::VerbTrigger { verb_id: verb.id, input_invocation: parts.verb_invocation, }; } if let Some(invocation_parser) = &verb.invocation_parser { let exec_builder = ExecutionStringBuilder::without_invocation( sel_info, app_state, ); let verb_invocation = exec_builder.invocation_with_default( &invocation_parser.invocation_pattern, con, ); let mut parts = parts; parts.verb_invocation = Some(verb_invocation); self.set_content(&parts.to_string()); return Command::VerbEdit(parts.verb_invocation.unwrap()); } } // input field management if mode == Mode::Input && self.input_field.apply_timed_event(&timed_event) { return Command::from_raw(self.input_field.get_content(), false); } Command::None } } broot-1.46.3/src/command/parts.rs000064400000000000000000000314471046102023000147610ustar 00000000000000use { crate::{ pattern::*, verb::VerbInvocation, }, bet::BeTree, std::fmt, }; /// An intermediate parsed representation of the raw string #[derive(Debug, Clone, PartialEq)] pub struct CommandParts { pub raw_pattern: String, // may be empty pub pattern: BeTree, pub verb_invocation: Option, // may be empty if user typed the separator but no char after } impl fmt::Display for CommandParts { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.raw_pattern)?; if let Some(invocation) = &self.verb_invocation { write!(f, "{invocation}")?; } Ok(()) } } impl CommandParts { pub fn has_not_empty_verb_invocation(&self) -> bool { self.verb_invocation .as_ref() .is_some_and(|vi| !vi.is_empty()) } pub fn from>(raw: S) -> Self { let mut raw = raw.into(); let mut invocation_start_pos: Option = None; let mut pt = BeTree::new(); let mut chars = raw.char_indices().peekable(); let mut escape_cur_char = false; let mut escape_next_char = false; // we loop on chars and build the pattern tree until we reach an unescaped ' ' or ':' while let Some((pos, cur_char)) = chars.next() { let between_slashes = pt.current_atom() .is_some_and( |pp: &PatternParts| pp.is_between_slashes(), ); match cur_char { c if escape_cur_char => { // Escaping is used to prevent characters from being consumed at the // composite pattern level (or, and, parens) or as the separator between // the pattern and the verb. An escaped char is usable in a pattern atom. pt.mutate_or_create_atom(PatternParts::default).push(c); } '\\' => { // Pattern escaping rules: // - when after '/': only ' ', ':', '/' and '\' need escaping // - otherwise, '&,' '|', '(', ')' need escaping too ('(' is only here for // symmetry) let between_slashes = match pt.current_atom() { Some(pattern_parts) => pattern_parts.is_between_slashes(), None => false, }; escape_next_char = match chars.peek() { None => false, // End of the string, we can't be escaping Some((_, next_char)) => match (next_char, between_slashes) { (' ' | ':' | '/' | '\\', _) => true, ('&' | '|' | '!' | '(' | ')', false) => true, _ => false, } }; if !escape_next_char { // if the '\' isn't used for escaping, it's used as its char value pt.mutate_or_create_atom(PatternParts::default).push('\\'); } } ':' => { if matches!(chars.peek(), Some((_,':'))) { // two successive ':' in pattern position are part of the // pattern pt.mutate_or_create_atom(PatternParts::default).push(':'); escape_next_char = true; } else { // ending the pattern part invocation_start_pos = Some(pos); break; } } ' ' => { // ending the pattern part invocation_start_pos = Some(pos); break; } '/' => { // starting an atom part pt.mutate_or_create_atom(PatternParts::default).add_part(); } '|' if !between_slashes && pt.accept_binary_operator() => { pt.push_operator(PatternOperator::Or); } '&' if !between_slashes && pt.accept_binary_operator() => { pt.push_operator(PatternOperator::And); } '!' if !between_slashes && pt.accept_unary_operator() => { pt.push_operator(PatternOperator::Not); } '(' if !between_slashes && pt.accept_opening_par() => { pt.open_par(); } ')' if !between_slashes && pt.accept_closing_par() => { pt.close_par(); } _ => { pt.mutate_or_create_atom(PatternParts::default).push(cur_char); } } escape_cur_char = escape_next_char; escape_next_char = false; } let mut verb_invocation = None; if let Some(pos) = invocation_start_pos { verb_invocation = Some(VerbInvocation::from( raw[pos + 1..].trim_start() // allowing extra spaces )); raw.truncate(pos); } CommandParts { raw_pattern: raw, pattern: pt, verb_invocation, } } /// split an input into its two possible parts, the pattern /// and the verb invocation. Each part, when defined, is /// suitable to create a command on its own. pub fn split(mut self) -> (Option, Option) { let verb_invocation = self.verb_invocation.take(); ( if self.raw_pattern.is_empty() { None } else { Some(CommandParts { raw_pattern: self.raw_pattern, pattern: self.pattern, verb_invocation: None, }) }, verb_invocation.map(|verb_invocation| CommandParts { raw_pattern: String::new(), pattern: BeTree::new(), verb_invocation: Some(verb_invocation), }), ) } } #[cfg(test)] mod test_command_parts { use { crate::{ command::CommandParts, pattern::*, verb::VerbInvocation, }, bet::{BeTree, Token}, }; fn pp(a: &[&str]) -> PatternParts { a.try_into().unwrap() } /// Check that the input is parsed as expected: /// - a raw pattern /// - the token (operators and pattern_parts) of the pattern /// - the verb invocation fn check( input: &str, raw_pattern: &str, mut pattern_tokens: Vec>, verb_invocation: Option<&str>, ) { let mut pattern = BeTree::new(); for token in pattern_tokens.drain(..) { pattern.push(token); } let left = CommandParts { raw_pattern: raw_pattern.to_string(), pattern, verb_invocation: verb_invocation.map(VerbInvocation::from), }; dbg!(&left); let right = CommandParts::from(input); dbg!(&right); assert_eq!(left, right); } #[test] fn parse_empty() { check( "", "", vec![], None, ); } #[test] fn parse_just_semicolon() { check( ":", "", vec![], Some(""), ); } #[test] fn parse_no_pattern() { check( " cd /", "", vec![], Some("cd /"), ); } #[test] fn parse_pattern_and_invocation() { check( "/r cd /", "/r", vec![ Token::Atom(pp(&["", "r"])), ], Some("cd /"), ); } #[test] fn allow_extra_spaces_before_invocation() { check( " cd /", "", vec![], Some("cd /"), ); check( "/r cd /", "/r", vec![ Token::Atom(pp(&["", "r"])), ], Some("cd /"), ); check( r#"a\ b e"#, r#"a\ b"#, vec![ Token::Atom(pp(&["a b"])), ], Some("e"), ); check( "/a: b", "/a", vec![ Token::Atom(pp(&["", "a"])), ], Some("b"), ); } #[test] fn parse_pattern_between_slashes() { check( r#"/&"#, r#"/&"#, vec![ Token::Atom(pp(&["", "&"])), ], None, ); check( r#"/&/&r/a(\w-)+/ rm"#, r#"/&/&r/a(\w-)+/"#, vec![ Token::Atom(pp(&["", "&", ""])), Token::Operator(PatternOperator::And), Token::Atom(pp(&["r", r#"a(\w-)+"#, ""])), ], Some("rm"), ); } #[test] fn parse_pattern_with_space() { check( r#"a\ b"#, r#"a\ b"#, vec![ Token::Atom(pp(&["a b"])), ], None, ); } #[test] fn parse_pattern_with_slash() { check( r#"r/a\ b\//i cd /"#, r#"r/a\ b\//i"#, vec![ Token::Atom(pp(&["r", "a b/", "i"])), ], Some("cd /"), ); } #[test] fn parse_fuzzy_pattern_searching_parenthesis() { check( r#"\("#, r#"\("#, vec![ Token::Atom(pp(&["("])), ], None, ); } #[test] fn parse_regex_pattern_searching_parenthesis() { check( r#"/\("#, r#"/\("#, vec![ Token::Atom(pp(&["", r#"\("#])), ], None, ); } #[test] fn parse_composite_pattern() { check( "(/txt$/&!truc)&c/rex", "(/txt$/&!truc)&c/rex", vec![ Token::OpeningParenthesis, Token::Atom(pp(&["", "txt$", ""])), Token::Operator(PatternOperator::And), Token::Operator(PatternOperator::Not), Token::Atom(pp(&["truc"])), Token::ClosingParenthesis, Token::Operator(PatternOperator::And), Token::Atom(pp(&["c", "rex"])), ], None ); } #[test] fn parse_unclosed_composite_pattern() { check( r#"!/\.json$/&(c/isize/|c/i32:rm"#, r#"!/\.json$/&(c/isize/|c/i32"#, vec![ Token::Operator(PatternOperator::Not), Token::Atom(pp(&["", r#"\.json$"#, ""])), Token::Operator(PatternOperator::And), Token::OpeningParenthesis, Token::Atom(pp(&["c", "isize", ""])), Token::Operator(PatternOperator::Or), Token::Atom(pp(&["c", "i32"])), ], Some("rm"), ); } #[test] fn issue_592() { // https://github.com/Canop/broot/issues/592 check( r#"\t"#, r#"\t"#, vec![ Token::Atom(pp(&[r#"\t"#])), ], None, ); check( r#"r/@(\.[^.]+)+/ cp .."#, r#"r/@(\.[^.]+)+/"#, vec![ Token::Atom(pp(&["r", r#"@(\.[^.]+)+"#, ""])), ], Some("cp .."), ); } // two colons in pattern positions are something the user searches #[test] fn allow_non_escaped_double_colon() { check( r#"::"#, r#"::"#, vec![ Token::Atom(pp(&[r#"::"#])), ], None, ); check( r#":::"#, r#"::"#, vec![ Token::Atom(pp(&[r#"::"#])), ], Some(""), ); check( r#":::cd c:\"#, r#"::"#, vec![ Token::Atom(pp(&[r#"::"#])), ], Some(r#"cd c:\"#), ); check( r#"and::Sc:cd c:\"#, r#"and::Sc"#, vec![ Token::Atom(pp(&[r#"and::Sc"#])), ], Some(r#"cd c:\"#), ); check( r#"!:: "#, r#"!::"#, vec![ Token::Operator(PatternOperator::Not), Token::Atom(pp(&[r#"::"#])), ], Some(""), ); check( r#"::a:rm"#, r#"::a"#, vec![ Token::Atom(pp(&[r#"::a"#])), ], Some("rm"), ); } } broot-1.46.3/src/command/scroll.rs000064400000000000000000000017471046102023000151260ustar 00000000000000 #[derive(Debug, Clone, Copy)] pub enum ScrollCommand { Lines(i32), Pages(i32), } impl ScrollCommand { pub fn to_lines(self, page_height: usize) -> i32 { match self { Self::Lines(n) => n, Self::Pages(n) => n * page_height as i32, } } pub fn is_up(self) -> bool { match self { Self::Lines(n) => n < 0, Self::Pages(n) => n < 0, } } /// compute the new scroll value pub fn apply( self, scroll: usize, content_height: usize, page_height: usize, ) -> usize { (scroll as i32 + self.to_lines(page_height)) .min(content_height as i32 - page_height as i32 + 1) .max(0) as usize } pub fn is_thumb(y: u16, scrollbar: Option<(u16, u16)>) -> bool { if let Some((sctop, scbottom)) = scrollbar { if sctop <= y && y <= scbottom { return true; } } false } } broot-1.46.3/src/command/sel.rs000064400000000000000000000010541046102023000144020ustar 00000000000000 /// compute a new selection index for the given list len, /// taking into account whether we should cycle or not pub fn move_sel( selection: usize, len: usize, d: i32, // displacement cycle: bool, ) -> usize { if len == 0 { return 0; } let ns = (selection as i32) + d; if ns < 0 { if cycle { len - 1 } else { 0 } } else if ns >= len as i32 { if cycle { 0 } else { len - 1 } } else { ns as usize } } broot-1.46.3/src/command/sequence.rs000064400000000000000000000100141046102023000154230ustar 00000000000000//! this mod achieves the transformation of a string containing //! one or several commands into a vec of parsed commands use { super::{ Command, CommandParts, }, crate::{ app::AppContext, errors::ProgramError, verb::*, }, }; /// an unparsed sequence with its separator (which may be /// different from the one provided by local_separator()) #[derive(Debug, Clone)] pub struct Sequence { pub raw: String, pub separator: String, } impl Sequence { /// return the separator to use to parse sequences. pub fn local_separator() -> String { match std::env::var("BROOT_CMD_SEPARATOR") { Ok(sep) if !sep.is_empty() => sep, _ => String::from(";"), } } pub fn new>( raw: S, separator: Option, ) -> Self { Self { raw: raw.into(), separator: separator.map_or_else(Sequence::local_separator, |s| s.into()), } } pub fn new_single(cmd: String) -> Self { Self { separator: "".to_string(), raw: cmd, } } pub fn new_local(raw: String) -> Self { Self { separator: Self::local_separator(), raw, } } /// Parse the sequence into a vec of commands. /// /// Beware: panel_state_type filtering isn't applied ( /// and would be difficult a priori as we can change panel /// in the middle of a sequence) pub fn parse( &self, con: &AppContext, ) -> Result, ProgramError> { debug!("Splitting cmd sequence with {:?}", &self.separator); let mut commands = Vec::new(); if self.separator.is_empty() { add_commands(&self.raw, &mut commands, con)?; } else { for input in self.raw.split(&self.separator) { add_commands(input, &mut commands, con)?; } } Ok(commands) } pub fn has_selection_group(&self) -> bool { str_has_selection_group(&self.raw) } pub fn has_other_panel_group(&self) -> bool { str_has_other_panel_group(&self.raw) } } /// Add commands to a sequence. /// /// An input may be made of two parts: /// - a search pattern /// - a verb followed by its arguments /// /// We need to build a command for each part so /// that the search is effectively done before /// the verb invocation fn add_commands( input: &str, commands: &mut Vec<(String, Command)>, con: &AppContext, ) -> Result<(), ProgramError> { let raw_parts = CommandParts::from(input.to_string()); let (pattern, verb_invocation) = raw_parts.split(); if let Some(pattern) = pattern { commands.push((input.to_string(), Command::from_parts(pattern, false))); } if let Some(verb_invocation) = verb_invocation { let mut command = Command::from_parts(verb_invocation, true); if let Command::VerbInvocate(invocation) = &command { // we check that the verb exists to avoid running a sequence // of actions with some missing match con.verb_store.search_prefix(&invocation.name, None) { PrefixSearchResult::NoMatch => { return Err(ProgramError::UnknownVerb { name: invocation.name.to_string(), }); } PrefixSearchResult::Matches(_) => { return Err(ProgramError::AmbiguousVerbName { name: invocation.name.to_string(), }); } PrefixSearchResult::Match(_, verb) => { if let Some(internal) = verb.get_internal() { command = Command::Internal { internal, input_invocation: Some(invocation.clone()), }; } commands.push((input.to_string(), command)); } } } } Ok(()) } broot-1.46.3/src/command/trigger_type.rs000064400000000000000000000007201046102023000163220ustar 00000000000000use { crate::verb::Verb, }; /// This rather vague enum might be precised or removed. It /// serves today to characterize whether a verb execution /// comes from the input or not (in this case the input is /// consumed and cleared when the verb is executed). #[derive(Debug, Clone, Copy, PartialEq)] pub enum TriggerType<'v> { /// the verb was typed in the input and user has hit enter. Input(&'v Verb), /// probably a key shortcut Other, } broot-1.46.3/src/conf/conf.rs000064400000000000000000000220201046102023000140470ustar 00000000000000//! manage reading the verb shortcuts from the configuration file, //! initializing if if it doesn't yet exist use { super::*, crate::{ app::Mode, display::{ColsConf, LayoutInstructions}, errors::{ConfError, ProgramError}, kitty::TransmissionMedium, path::{ path_from, PathAnchor, }, preview::PreviewTransformerConf, skin::SkinEntry, syntactic::SyntaxTheme, verb::ExecPattern, }, rustc_hash::FxHashMap, crokey::crossterm::style::Attribute, serde::Deserialize, std::{ collections::HashMap, num::NonZeroUsize, path::PathBuf, }, }; macro_rules! overwrite { ($dst: ident, $prop: ident, $src: ident) => { if $src.$prop.is_some() { $dst.$prop = $src.$prop.take(); } }; } macro_rules! overwrite_map { ($dst: ident, $prop: ident, $src: ident) => { for (k, v) in $src.$prop { $dst.$prop.insert(k, v); } }; } macro_rules! overwrite_vec { ($dst: ident, $prop: ident, $src: ident) => { for v in $src.$prop { $dst.$prop.push(v); } }; } /// The configuration read from conf.toml or conf.hjson file(s) #[derive(Default, Clone, Debug, Deserialize)] pub struct Conf { #[serde(alias="capture-mouse")] pub capture_mouse: Option, #[serde(alias="cols-order")] pub cols_order: Option, #[serde(alias="content-search-max-file-size", deserialize_with="file_size::deserialize", default)] pub content_search_max_file_size: Option, #[serde(alias="date-time-format")] pub date_time_format: Option, #[serde(alias="default-flags")] pub default_flags: Option, // the flags to apply before cli ones /// Obsolete, kept for compatibility: you should now use capture_mouse #[serde(alias="disable-mouse-capture")] pub disable_mouse_capture: Option, #[serde(alias="enable-keyboard-enhancements")] pub enable_kitty_keyboard: Option, #[serde(default, alias="ext-colors")] pub ext_colors: FxHashMap, pub file_sum_threads_count: Option, /// the files used to load this configuration #[serde(skip)] pub files: Vec, #[serde(alias="icon-theme")] pub icon_theme: Option, #[serde(default)] pub imports: Vec, /// the initial mode (only relevant when modal is true) #[serde(alias="initial-mode")] pub initial_mode: Option, #[serde(alias="kitty-graphics-transmission")] pub kitty_graphics_transmission: Option, #[serde(default, alias="kept-kitty-temp-files")] pub kept_kitty_temp_files: Option, #[serde(default, alias="preview-transformers")] pub preview_transformers: Vec, #[serde(alias="lines-after-match-in-preview")] pub lines_after_match_in_preview: Option, #[serde(alias="lines-before-match-in-preview")] pub lines_before_match_in_preview: Option, pub max_panels_count: Option, #[serde(alias="max_staged_count")] pub max_staged_count: Option, pub modal: Option, #[serde(alias="quit-on-last-cancel")] pub quit_on_last_cancel: Option, #[serde(alias="search-modes")] pub search_modes: Option>, #[serde(alias="show-matching-characters-on-path-searches")] pub show_matching_characters_on_path_searches: Option, #[serde(alias="show-selection-mark")] pub show_selection_mark: Option, pub skin: Option>, #[serde(default, alias="special-paths")] pub special_paths: HashMap, #[serde(alias="syntax-theme")] pub syntax_theme: Option, #[serde(alias="terminal-title")] pub terminal_title: Option, #[serde(alias="reset-terminal-title-on-exit")] pub reset_terminal_title_on_exit: Option, #[serde(alias="true-colors")] pub true_colors: Option, #[serde(alias="update-work-dir")] pub update_work_dir: Option, #[serde(default)] pub verbs: Vec, #[serde(alias="layout-instructions")] pub layout_instructions: Option, // BEWARE: entries added here won't be usable unless also // added in read_file! } impl Conf { /// return the path to the default conf.toml file. /// If there's no conf.hjson file in the default conf directory, /// and if there's a toml file, return this toml file. pub fn default_location() -> PathBuf { let hjson_file = super::dir().join("conf.hjson"); if !hjson_file.exists() { let toml_file = super::dir().join("conf.toml"); if toml_file.exists() { return toml_file; } } // neither file exists, we return the default one hjson_file } /// read the configuration file from the default OS specific location. /// Create it if it doesn't exist pub fn from_default_location() -> Result { let conf_dir = super::dir(); let conf_filepath = Conf::default_location(); if !conf_filepath.exists() { write_default_conf_in(conf_dir)?; println!( "New Configuration files written in {}{:?}{}.", Attribute::Bold, &conf_dir, Attribute::Reset, ); println!("You should have a look at them: their comments will help you configure broot."); println!("You should especially set up your favourite editor in verbs.hjson."); } let mut conf = Conf::default(); conf.read_file(conf_filepath)?; Ok(conf) } pub fn solve_conf_path(&self, path: &str) -> Option { if path.ends_with(".toml") || path.ends_with(".hjson") { for conf_file in self.files.iter().rev() { let solved = path_from(conf_file, PathAnchor::Parent, path); if solved.exists() { return Some(solved) } } } None } /// read the configuration from a given path. Assume it exists. /// Values set in the read file replace the ones of self. /// Errors are printed on stderr (assuming this function is called /// before terminal alternation). pub fn read_file( &mut self, path: PathBuf, ) -> Result<(), ProgramError> { debug!("reading conf file: {:?}", &path); let mut conf: Conf = SerdeFormat::read_file(&path)?; overwrite!(self, default_flags, conf); overwrite!(self, date_time_format, conf); overwrite!(self, icon_theme, conf); overwrite!(self, syntax_theme, conf); overwrite!(self, disable_mouse_capture, conf); overwrite!(self, capture_mouse, conf); overwrite!(self, true_colors, conf); overwrite!(self, show_selection_mark, conf); overwrite!(self, cols_order, conf); overwrite!(self, skin, conf); overwrite!(self, search_modes, conf); overwrite!(self, max_panels_count, conf); overwrite!(self, modal, conf); overwrite!(self, initial_mode, conf); overwrite!(self, quit_on_last_cancel, conf); overwrite!(self, file_sum_threads_count, conf); overwrite!(self, max_staged_count, conf); overwrite!(self, show_matching_characters_on_path_searches, conf); overwrite!(self, content_search_max_file_size, conf); overwrite!(self, terminal_title, conf); overwrite!(self, reset_terminal_title_on_exit, conf); overwrite!(self, update_work_dir, conf); overwrite!(self, enable_kitty_keyboard, conf); overwrite!(self, kitty_graphics_transmission, conf); overwrite!(self, kept_kitty_temp_files, conf); overwrite!(self, lines_after_match_in_preview, conf); overwrite!(self, lines_before_match_in_preview, conf); overwrite!(self, layout_instructions, conf); self.verbs.append(&mut conf.verbs); // the following prefs are "additive": we can add entries from several // config files and they still make sense overwrite_map!(self, special_paths, conf); overwrite_map!(self, ext_colors, conf); overwrite_vec!(self, preview_transformers, conf); self.files.push(path); // read the imports for import in &conf.imports { let file = import.file().trim(); if !import.applies() { debug!("skipping not applying conf file : {:?}", file); continue; } let import_path = self.solve_conf_path(file) .ok_or_else(|| ConfError::ImportNotFound { path: file.to_string() })?; if self.files.contains(&import_path) { debug!("skipping import already read: {:?}", import_path); continue; } self.read_file(import_path)?; } Ok(()) } } broot-1.46.3/src/conf/default.rs000064400000000000000000000036731046102023000145630ustar 00000000000000use { include_dir::{Dir, DirEntry, include_dir}, std::{ fs, io, path::Path, }, }; static DEFAULT_CONF_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/resources/default-conf"); /// Write the default configuration files in the destination directory, not /// overwriting existing ones pub fn write_default_conf_in(dir: &Path) -> Result<(), io::Error> { info!("writing default conf in {:?}", dir); if dir.exists() && !dir.is_dir() { return Err(io::Error::new( io::ErrorKind::Other, format!("{dir:?} isn't a directory"), )); } let mut files = Vec::new(); find_files(&DEFAULT_CONF_DIR, &mut files); for file in files { let dest_path = dir.join(file.path()); if dest_path.exists() { warn!("not overwriting {:?}", dest_path); } else { if let Some(dir) = dest_path.parent() { if !dir.exists() { fs::create_dir_all(dir)?; } }; info!("writing file {:?}", file.path()); fs::write(dest_path, file.contents())?; } } Ok(()) } fn find_files<'d>(dir: &'d Dir<'d>, files: &mut Vec<&'d include_dir::File<'d>>) { for entry in dir.entries() { match entry { DirEntry::Dir(sub_dir) => { find_files(sub_dir, files); } DirEntry::File(file) => { files.push(file); } } } } /// Check that all the files in the default_conf directory are valid /// configuration files #[test] fn check_default_conf_files() { use crate::conf::*; let mut files = Vec::new(); find_files(&DEFAULT_CONF_DIR, &mut files); for file in files { println!("Checking {}", file.path().display()); let file_content = std::str::from_utf8(file.contents()).unwrap(); SerdeFormat::read_string::(file.path(), file_content).unwrap(); } } broot-1.46.3/src/conf/default_flags.rs000064400000000000000000000012521046102023000157260ustar 00000000000000use { crate::{ cli::Args, errors::ConfError, }, clap::Parser, lazy_regex::*, }; /// parse the 'default_flags' parameter of a conf. pub fn parse_default_flags(s: &str) -> Result { let prefixed; let mut tokens: Vec<&str> = if regex_is_match!("^[a-zA-Z]+$", s) { // this covers the old syntax like `default_flags: gh` prefixed = format!("-{s}"); vec![&prefixed] } else { splitty::split_unquoted_whitespace(s).collect() }; tokens.insert(0, "broot"); Args::try_parse_from(&tokens) .map_err(|_| ConfError::InvalidDefaultFlags { flags: s.to_string() }) } broot-1.46.3/src/conf/file_size.rs000064400000000000000000000033031046102023000150760ustar 00000000000000use::serde::{ de, Deserialize, }; pub fn parse_file_size(input: &str) -> Result { let s = input.to_lowercase(); let s = s.trim_end_matches('b'); let (s, binary) = match s.strip_suffix('i') { Some(s) => (s, true), None => (s, false), }; let cut = s.find(|c: char| !(c.is_ascii_digit() || c=='.')); let (digits, factor): (&str, u64) = match cut { Some(idx) => ( &s[..idx], match (&s[idx..], binary) { ("k", false) => 1000, ("k", true) => 1024, ("m", false) => 1000*1000, ("m", true) => 1024*1024, ("g", false) => 1000*1000*1000, ("g", true) => 1024*1024*1024, ("t", false) => 1000*1000*1000*1000, ("t", true) => 1024*1024*1024*1024, _ => { // it's not a number return Err(format!("{input:?} can't be parsed as file size")); } } ), None => (s, 1), }; match digits.parse::() { Ok(n) => Ok((n * factor as f64).ceil() as u64), _ => Err(format!("{input:?} can't be parsed as file size")) } } #[test] fn test_parse_file_size(){ assert_eq!(parse_file_size("33"), Ok(33)); assert_eq!(parse_file_size("55G"), Ok(55_000_000_000)); assert_eq!(parse_file_size("2kb"), Ok(2_000)); assert_eq!(parse_file_size("1.23kiB"), Ok(1260)); } pub fn deserialize<'de, D>(d: D) -> Result, D::Error> where D: de::Deserializer<'de> { as Deserialize>::deserialize(d)? .map(|s| parse_file_size(&s).map_err(de::Error::custom)) .transpose() } broot-1.46.3/src/conf/format.rs000064400000000000000000000040711046102023000144200ustar 00000000000000use { crate::{ errors::{ConfError, ProgramError}, }, serde::de::DeserializeOwned, std::{ fs, path::Path, }, deser_hjson, toml, }; /// Formats usable for reading configuration files #[derive(Default, PartialEq, Eq, Debug, Clone, Copy)] pub enum SerdeFormat { #[default] Hjson, Toml, } pub static FORMATS: &[SerdeFormat] = &[ SerdeFormat::Hjson, SerdeFormat::Toml, ]; impl SerdeFormat { pub fn key(self) -> &'static str { match self { Self::Hjson => "hjson", Self::Toml => "toml", } } pub fn from_key(key: &str) -> Option { match key { "hjson" => Some(SerdeFormat::Hjson), "toml" => Some(SerdeFormat::Toml), _ => None, } } pub fn from_path(path: &Path) -> Result { path.extension() .and_then(|os| os.to_str()) .map(|ext| ext.to_lowercase()) .and_then(|key| Self::from_key(&key)) .ok_or_else(|| ConfError::UnknownFileExtension { path: path.to_string_lossy().to_string() }) } pub fn read_string(path: &Path, s: &str) -> Result where T: DeserializeOwned { let format = Self::from_path(path)?; match format { Self::Hjson => { deser_hjson::from_str::(s) .map_err(|e| ProgramError::ConfFile { path: path.to_string_lossy().to_string(), details: e.into(), }) } Self::Toml => { toml::from_str::(s) .map_err(|e| ProgramError::ConfFile { path: path.to_string_lossy().to_string(), details: e.into(), }) } } } pub fn read_file(path: &Path) -> Result where T: DeserializeOwned { let file_content = fs::read_to_string(path)?; Self::read_string(path, &file_content) } } broot-1.46.3/src/conf/import.rs000064400000000000000000000017361046102023000144470ustar 00000000000000use { crate::{ display::LumaCondition, }, serde::Deserialize, }; /// A file to import, with optionally a condition #[derive(Clone, Debug, Deserialize)] #[serde(untagged)] pub enum Import { Simple(String), Detailed(DetailedImport), } #[derive(Clone, Debug, Deserialize)] pub struct DetailedImport { /// a condition on terminal light pub luma: Option, /// path, either absolute or relative to the current file /// or the conf directory pub file: String, } impl Import { pub fn applies(&self) -> bool { self.luma().map_or(true, |luma| luma.is_verified()) } pub fn luma(&self) -> Option<&LumaCondition> { match self { Self::Simple(_) => None, Self::Detailed(detailed) => detailed.luma.as_ref(), } } pub fn file(&self) -> &str { match self { Self::Simple(s) => s, Self::Detailed(detailed) => &detailed.file } } } broot-1.46.3/src/conf/mod.rs000064400000000000000000000045471046102023000137170ustar 00000000000000use { crate::path::untilde, directories, once_cell::sync::Lazy, std::path::{Path, PathBuf}, }; mod conf; mod default; mod default_flags; mod format; pub mod file_size; mod import; mod special_handling_conf; mod verb_conf; pub use { conf::Conf, default::write_default_conf_in, default_flags::*, format::*, import::*, special_handling_conf::*, verb_conf::VerbConf, }; /// return the instance of ProjectDirs holding broot's specific paths pub fn app_dirs() -> directories::ProjectDirs { directories::ProjectDirs::from("org", "dystroy", "broot") .expect("Unable to find configuration directories") } fn env_conf_dir() -> Option { std::env::var("BROOT_CONFIG_DIR") .ok() .as_deref() .map(untilde) } #[cfg(not(target_os = "macos"))] fn find_conf_dir() -> PathBuf { env_conf_dir() .unwrap_or_else(|| app_dirs().config_dir().to_path_buf()) } #[cfg(target_os = "macos")] fn find_conf_dir() -> PathBuf { if let Some(env_dir) = env_conf_dir() { return env_dir; } else if let Some(user_dirs) = directories::UserDirs::new() { // We first search in ~/.config/broot which should be the preferred solution let preferred = user_dirs.home_dir().join(".config/broot"); if preferred.exists() { return preferred; } // The directories crate has a non usual choice of config directory, // especially for a CLI application. We use it only when // the preferred directory doesn't exist and this one exists. // See https://github.com/Canop/broot/issues/103 let second_choice = app_dirs().config_dir().to_path_buf(); if second_choice.exists() { // An older version of broot was used to write the // config, we don't want to lose it. return second_choice; } // Either the config has been scraped or it's a new installation return preferred; } else { // there's no home. There are probably other problems too but here we // are just looking for a place for our config, not for a shelter for all // so the default will do app_dirs().config_dir().to_path_buf() } } static CONF_DIR: Lazy = Lazy::new(find_conf_dir); /// return the path to the config directory pub fn dir() -> &'static Path { &CONF_DIR } broot-1.46.3/src/conf/special_handling_conf.rs000064400000000000000000000056061046102023000174260ustar 00000000000000use { crate::{ errors::ConfError, path::*, }, directories::UserDirs, lazy_regex::*, serde::Deserialize, std::collections::HashMap, }; type SpecialPathsConf = HashMap; #[derive(Clone, Debug, Deserialize, Hash, PartialEq, Eq)] #[serde(transparent)] pub struct GlobConf { pub pattern: String, } #[derive(Clone, Copy, Debug, Deserialize)] #[serde(untagged)] pub enum SpecialHandlingConf { Shortcut(SpecialHandlingShortcut), Detailed(SpecialHandling), } #[derive(Clone, Debug, Copy, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum SpecialHandlingShortcut { None, Enter, #[serde(alias = "no-enter")] NoEnter, Hide, #[serde(alias = "no-hide")] NoHide, } impl From for SpecialHandling { fn from(shortcut: SpecialHandlingShortcut) -> Self { use Directive::*; match shortcut { SpecialHandlingShortcut::None => SpecialHandling { show: Default, list: Default, sum: Default, }, SpecialHandlingShortcut::Enter => SpecialHandling { show: Always, list: Always, sum: Always, }, SpecialHandlingShortcut::NoEnter => SpecialHandling { show: Default, list: Never, sum: Never, }, SpecialHandlingShortcut::Hide => SpecialHandling { show: Never, list: Default, sum: Never, }, SpecialHandlingShortcut::NoHide => SpecialHandling { show: Always, list: Default, sum: Default, }, } } } impl From for SpecialHandling { fn from(conf: SpecialHandlingConf) -> Self { match conf { SpecialHandlingConf::Shortcut(shortcut) => shortcut.into(), SpecialHandlingConf::Detailed(handling) => handling, } } } impl TryFrom<&SpecialPathsConf> for SpecialPaths { type Error = ConfError; fn try_from(map: &SpecialPathsConf) -> Result { let mut entries = Vec::new(); for (k, v) in map { entries.push(SpecialPath::new(k.to_glob()?, (*v).into())); } Ok(Self { entries }) } } impl GlobConf { pub fn to_glob(&self) -> Result { let s = regex_replace!(r"^~(/|$)", &self.pattern, |_, sep| { match UserDirs::new() { Some(dirs) => format!("{}{}", dirs.home_dir().to_string_lossy(), sep), None => "~/".to_string(), } }); let glob = if s.starts_with('/') || s.starts_with('~') { glob::Pattern::new(&s) } else { let pattern = format!("**/{}", &s); glob::Pattern::new(&pattern) }; glob.map_err(|_| ConfError::InvalidGlobPattern { pattern: self.pattern.to_string(), }) } } broot-1.46.3/src/conf/verb_conf.rs000064400000000000000000000023521046102023000150730ustar 00000000000000use { crate::{ app::{ PanelStateType, }, verb::*, }, serde::{Deserialize, Serialize}, }; /// A deserializable verb entry in the configuration #[derive(Default, Debug, Clone, Deserialize, Serialize)] pub struct VerbConf { pub invocation: Option, pub internal: Option, pub external: Option, pub execution: Option, pub cmd: Option, pub cmd_separator: Option, pub key: Option, #[serde(default, skip_serializing_if = "Vec::is_empty")] pub keys: Vec, #[serde(default, skip_serializing_if = "Vec::is_empty")] pub extensions: Vec, pub shortcut: Option, pub leave_broot: Option, pub from_shell: Option, #[serde(default, skip_serializing_if = "FileTypeCondition::is_default")] pub apply_to: FileTypeCondition, pub set_working_dir: Option, pub working_dir: Option, pub description: Option, pub auto_exec: Option, pub switch_terminal: Option, #[serde(default, skip_serializing_if = "Vec::is_empty")] pub panels: Vec, pub refresh_after: Option, } broot-1.46.3/src/content_search/content_match.rs000064400000000000000000000036571046102023000200410ustar 00000000000000 /// a displayable representation of where /// the needle was found, with some text around #[derive(Debug, Clone)] pub struct ContentMatch { pub extract: String, pub needle_start: usize, // position in the extract, in bytes pub needle_end: usize, // length in bytes } impl ContentMatch { pub fn build( hay: &[u8], pos: usize, // position in the hay needle: &str, desired_len: usize, // max length of the extract in bytes ) -> Self { if hay.is_empty() { // this happens if you search `cr/.*` and a file starts with an empty line return Self { extract: "".to_string(), needle_start: 0, needle_end: 0, }; } let mut extract_start = pos; let mut extract_end = pos + needle.len(); // not included loop { if extract_start == 0 || extract_end - extract_start >= desired_len / 2 { break; } let c = hay[extract_start - 1]; if c < 32 { break; } extract_start -= 1; } // left trimming while (hay[extract_start] == 32) && extract_start < pos { extract_start += 1; } loop { if extract_end == hay.len() || extract_end - extract_start >= desired_len { break; } let c = hay[extract_end]; if c < 32 { break; } extract_end += 1; } // at this point we're unsure whether we start at a correct char boundary, hence // the from_utf8_lossy let extract = String::from_utf8_lossy(&hay[extract_start..extract_end]).to_string(); let needle_start = extract.find(needle).unwrap_or(0); Self { extract, needle_start, needle_end: needle_start + needle.len(), } } } broot-1.46.3/src/content_search/content_search_result.rs000064400000000000000000000007131046102023000215760ustar 00000000000000 /// result of a full text search #[derive(Debug, Clone, Copy, PartialEq)] pub enum ContentSearchResult { /// the needle has been found at the given pos Found { pos: usize, }, /// the needle hasn't been found NotFound, // no match /// the file wasn't searched because it's binary or too big NotSuitable, } impl ContentSearchResult { pub fn is_found(self) -> bool { matches!(self, Self::Found {..}) } } broot-1.46.3/src/content_search/mod.rs000064400000000000000000000043561046102023000157670ustar 00000000000000 mod content_match; mod content_search_result; mod needle; pub use { crate::content_type::{self, extensions, magic_numbers}, content_match::ContentMatch, content_search_result::ContentSearchResult, needle::Needle, std::io::{ BufRead, BufReader}, }; use { memmap2::Mmap, std::{ fs::File, io, path::Path, }, }; pub const DEFAULT_MAX_FILE_SIZE: usize = 10 * 1024 * 1024; pub fn get_mmap>(hay_path: P) -> io::Result { let file = File::open(hay_path.as_ref())?; let hay = unsafe { Mmap::map(&file)? }; Ok(hay) } /// return the memmap to the file except if it was determined /// that the file is binary (from its extension, size, or first bytes) /// or is too big pub fn get_mmap_if_suitable>(hay_path: P, max_size: usize) -> io::Result> { if let Some(ext) = hay_path.as_ref().extension().and_then(|s| s.to_str()) { if extensions::is_known_binary(ext) { return Ok(None); } } let hay = get_mmap(&hay_path)?; if hay.len() > max_size || magic_numbers::is_known_binary(&hay) { return Ok(None); } Ok(Some(hay)) } /// return true when the file looks suitable for searching as text. /// /// If a memmap will be needed afterwards, prefer to use `get_mmap_if_not_binary` /// which optimizes testing and getting the mmap. pub fn is_path_suitable>(path: P, max_size: usize) -> bool { let path = path.as_ref(); let Ok(metadata) = path.metadata() else { return false; }; if metadata.len() > max_size as u64 { return false; } content_type::is_file_text(path).unwrap_or(false) } /// Return the 1-indexed line number for the byte at position pos pub fn line_count_at_pos>(path: P, pos: usize) -> io::Result { let mut reader = BufReader::new(File::open(path)?); let mut line = String::new(); let mut line_count = 1; let mut bytes_count = 0; while reader.read_line(&mut line)? > 0 { bytes_count += line.len(); if bytes_count >= pos { return Ok(line_count); } line_count += 1; line.clear(); } Err(io::Error::new(io::ErrorKind::UnexpectedEof, "too short".to_string())) } broot-1.46.3/src/content_search/needle.rs000064400000000000000000000176201046102023000164420ustar 00000000000000// Don't look here for search functions to reuse or even for efficient or proven tricks. // Benchmarks proved that the approach here was fast in the context of broot but that's all. use { super::*, memmap2::Mmap, std::{ convert::TryInto, fmt, io, path::{Path}, }, }; /// a strict (non fuzzy, case sensitive) pattern which may /// be searched in file contents #[derive(Clone)] pub struct Needle { /// bytes of the searched string /// (guaranteed to be valid UTF8 by construct) bytes: Box<[u8]>, max_file_size: usize, } impl fmt::Debug for Needle { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Needle") .field("bytes", &self.bytes) .finish() } } impl Needle { pub fn new(pat: &str, max_file_size: usize) -> Self { let bytes = pat.as_bytes().to_vec().into_boxed_slice(); Self { bytes, max_file_size } } pub fn is_empty(&self) -> bool { self.bytes.is_empty() } pub fn as_str(&self) -> &str { unsafe { std::str::from_utf8_unchecked(&self.bytes) } } // no, it doesn't bring more than a few % in speed fn find_naive_1(&self, hay: &Mmap) -> Option { let n = self.bytes[0]; hay.iter().position(|&b| b == n) } /// look for matches of the needle when it's length is 2 /// /// Calling this function with a hay of less than 2 bytes may result in undefined behavior. fn find_naive_2(&self, mut pos: usize, hay: &Mmap) -> Option { let max_pos = hay.len() - 2; let b0 = self.bytes[0]; let b1 = self.bytes[1]; unsafe { while pos <= max_pos { if *hay.get_unchecked(pos) == b0 && *hay.get_unchecked(pos + 1) == b1 { return Some(pos); } pos += 1; } } None } /// look for matches of the needle when it's length is 3 /// /// Calling this function with a hay of less than 3 bytes may result in undefined behavior. fn find_naive_3(&self, mut pos: usize, hay: &Mmap) -> Option { let max_pos = hay.len() - 3; let b0 = self.bytes[0]; let b1 = self.bytes[1]; let b2 = self.bytes[2]; unsafe { while pos <= max_pos { if *hay.get_unchecked(pos) == b0 && *hay.get_unchecked(pos + 1) == b1 && *hay.get_unchecked(pos + 2) == b2 { return Some(pos); } pos += 1; } } None } /// look for matches of the needle when it's length is 4 /// /// Calling this function with a hay of less than 4 bytes may result in undefined behavior. fn find_naive_4(&self, mut pos: usize, hay: &Mmap) -> Option { use std::mem::transmute; let max_pos = hay.len() - 4; unsafe { let needle: u32 = transmute::<[u8; 4], u32>((&*self.bytes).try_into().unwrap()); while pos <= max_pos { if transmute::<[u8; 4], u32>((&hay[pos..pos + 4]).try_into().unwrap()) == needle { return Some(pos); } pos += 1; } } None } /// look for matches of the needle when it's length is 6 /// /// Calling this function with a hay of less than 6 bytes may result in undefined behavior. fn find_naive_6(&self, mut pos: usize, hay: &Mmap) -> Option { let max_pos = hay.len() - 6; let b0 = self.bytes[0]; let b1 = self.bytes[1]; let b2 = self.bytes[2]; let b3 = self.bytes[3]; let b4 = self.bytes[4]; let b5 = self.bytes[5]; unsafe { while pos <= max_pos { if *hay.get_unchecked(pos) == b0 && *hay.get_unchecked(pos + 1) == b1 && *hay.get_unchecked(pos + 2) == b2 && *hay.get_unchecked(pos + 3) == b3 && *hay.get_unchecked(pos + 4) == b4 && *hay.get_unchecked(pos + 5) == b5 { return Some(pos); } pos += 1; } } None } fn is_at_pos(&self, hay_stack: &Mmap, pos: usize) -> bool { unsafe { for (i, b) in self.bytes.iter().enumerate() { if hay_stack.get_unchecked(i + pos) != b { return false; } } } true } /// look for matches of the needle for any length fn find_naive(&self, mut pos: usize, hay: &Mmap) -> Option { let max_pos = hay.len() - self.bytes.len(); while pos <= max_pos { if self.is_at_pos(hay, pos) { return Some(pos); } pos += 1; } None } /// search the mem map to find the first occurrence of the needle. /// /// Known limit: if the file has an encoding where the needle would /// be represented in a way different than UTF-8, the needle won't /// be found (I noticed the problem with other grepping tools, too, /// which is understandable as detecting the encoding and translating /// the needle would multiply the search time). /// /// /// The exact search algorithm used here (I removed Boyer-Moore) /// and the optimizations (loop unrolling, etc.) don't really matter /// as their impact is dwarfed by the whole mem map related set /// of problems. An alternate implementation should probably focus /// on avoiding mem maps. fn search_mmap(&self, hay: &Mmap) -> ContentSearchResult { if hay.len() < self.bytes.len() { return ContentSearchResult::NotFound; } // we tell the system how we intent to use the mmap // to increase the likehod the memory is available // for our loop #[cfg(not(any(target_family = "windows", target_os = "android")))] unsafe { libc::posix_madvise( hay.as_ptr() as *mut std::ffi::c_void, hay.len(), libc::POSIX_MADV_SEQUENTIAL, ); // TODO the Windows equivalent might be PrefetchVirtualMemory } let pos = match self.bytes.len() { 1 => self.find_naive_1(hay), 2 => self.find_naive_2(0, hay), 3 => self.find_naive_3(0, hay), 4 => self.find_naive_4(0, hay), 6 => self.find_naive_6(0, hay), _ => self.find_naive(0, hay), }; pos.map_or( ContentSearchResult::NotFound, |pos| ContentSearchResult::Found { pos }, ) } /// determine whether the file contains the needle pub fn search>(&self, hay_path: P) -> io::Result { super::get_mmap_if_suitable(hay_path, self.max_file_size) .map(|om| om.map_or( ContentSearchResult::NotSuitable, |hay| self.search_mmap(&hay), )) } /// this is supposed to be called only when it's known that there's /// a match pub fn get_match>( &self, hay_path: P, desired_len: usize, ) -> Option { let hay = match get_mmap(hay_path) { Ok(hay) => hay, _ => { return None; } }; match self.search_mmap(&hay) { ContentSearchResult::Found { pos } => { Some(ContentMatch::build(&hay, pos, self.as_str(), desired_len)) } _ => None, } } } #[cfg(test)] mod content_search_tests { use super::*; #[test] fn test_found() -> Result<(), io::Error> { let needle = Needle::new("inception", 1_000_000); let res = needle.search("src/content_search/needle.rs")?; assert!(res.is_found()); Ok(()) } } broot-1.46.3/src/content_type/extensions.rs000064400000000000000000000031671046102023000171220ustar 00000000000000use { phf::{phf_set, Set}, }; /// a short list of extensions that shouldn't be searched /// by content /// /// If you feel this list should maybe be changed, contact /// me on miaou or raise an issue. static BINARY_EXTENSIONS: Set<&'static str> = phf_set! { "a", "aif", "AIF", "ap_", "apk", "bin", "BIN", "bmp", "BMP", "bzip", "BZIP", "bzip2", "BZIP2", "cab", "CAB", "class", "com", "COM", "crx", "dat", "DAT", "db", "DB", "dbf", "DBF", "deb", "doc", "DOC", "docx", "DOCX", "eps", "EPS", "exe", "EXE", "dll", "DLL", "gif", "GIF", "gz", "gzip", "ico", "ICO", "iso", "ISO", "jar", "JAR", "jpg", "JPG", "jpeg", "JPEG", "lz4", "LZ4", "mdb", "MDB", "mp3", "MP3", "mp4", "MP4", "mpa", "MPa", "mpg", "MPG", "mpeg", "MPEG", "msi", "MSI", "o", "odf", "ODF", "odp", "ODP", "ods", "ODS", "odt", "ODT", "ogg", "OGG", "pdb", "pdf", "PDF", "pkg", "PKG", "png", "PNG", "ppt", "PPT", "pptx", "PPTX", "psd", "PSD", "ps", "PS", "rar", "RAR", "rpm", "RPM", "rsrc", "rtf", "so", "tar", "tar.gz", "ttf", "TTF", "tgz", "TGZ", "xls", "XLS", "xlsx", "XLSX", "vob", "VOB", "vsd", "VSD", "vsdx", "VSDX", "war", "WAR", "wasm", "wav", "WAV", "woff", "WOFF", "woff2", "WOFF2", "zip", "ZIP", "z", "Z", }; /// tells whether the file extension is one of a file format /// which shouldn't be searched as text pub fn is_known_binary(ext: &str) -> bool { BINARY_EXTENSIONS.contains(ext) } broot-1.46.3/src/content_type/magic_numbers.rs000064400000000000000000000105711046102023000175330ustar 00000000000000 use { phf::{phf_set, Set}, std::{ path::Path, fs::File, io::{self, Read}, }, }; pub const MIN_FILE_SIZE: usize = 100; // those ones are now removed because of the extension filtering // static SIGNATURES_2: [[u8;2];2] = [ // [ 0x4D, 0x5A ], // exe, dll // [ 0x42, 0x4D ], // BMP - Is that still necessary ? // ]; // those ones are now removed because of the extension filtering // static SIGNATURES_3: [[u8;3];2] = [ // [ 0x49, 0x44, 0x33 ], // mp3 // [ 0x77, 0x4F, 0x46 ], // WOFF // ]; // signatures starting with 00, FF or FE don't need to be put here // note: the phf_set macro doesn't seem to allow u32 literals like 0x504B0304 static SIGNATURES_4: Set<[u8; 4]> = phf_set! { [ 0x50, 0x4B, 0x03, 0x04 ], // zip file format and formats based on it, such as EPUB, JAR, ODF, OOXML [ 0x50, 0x4B, 0x05, 0x06 ], // zip file format and formats based on it, such as EPUB, JAR, ODF, OOXML [ 0x50, 0x4B, 0x07, 0x08 ], // zip file format and formats based on it, such as EPUB, JAR, ODF, OOXML [ 0xED, 0xAB, 0xEE, 0xDB ], // rpm [ 0x49, 0x49, 0x2A, 0x00 ], // tif [ 0x4D, 0x4D, 0x00, 0x2A ], // tiff [ 0x7F, 0x45, 0x4C, 0x46 ], // elf [ 0xCA, 0xFE, 0xBA, 0xBE ], // java class [ 0x25, 0x21, 0x50, 0x53 ], // ps [ 0x4F, 0x67, 0x67, 0x53 ], // ogg [ 0x38, 0x42, 0x50, 0x53 ], // psd [ 0x57, 0x41, 0x56, 0x45 ], // wave [ 0x41, 0x56, 0x49, 0x20 ], // avi [ 0x4D, 0x54, 0x68, 0x64 ], // midi [ 0xD0, 0xCF, 0x11, 0xE0 ], // old MS Office things [ 0x43, 0x72, 0x32, 0x34 ], // old Chrome extensions [ 0x78, 0x61, 0x72, 0x21 ], // xar [ 0x75, 0x73, 0x74, 0x61 ], // tar [ 0x37, 0x7A, 0xBC, 0xAF ], // 7zip [ 0x4D, 0x53, 0x43, 0x46 ], // Microsoft Cabinet file [ 0x52, 0x49, 0x46, 0x46 ], // riff (including WebP) [ 0x47, 0x49, 0x46, 0x38 ], // gif (common start of GIF87a and GIF89a ) [ 0x4C, 0x5A, 0x49, 0x50 ], // lzip [ 0xCE, 0xFA, 0xED, 0xFE ], // Mach-O [ 0xCF, 0xFA, 0xED, 0xFE ], // Mach-O [ 0x46, 0x4C, 0x49, 0x46 ], // flif [ 0x62, 0x76, 0x78, 0x32 ], // lzfse }; // those ones are now removed because of the extension and size filterings // static SIGNATURES_5: [[u8;5];2] = [ // [ 0x25, 0x50, 0x44, 0x46, 0x2d ], // pdf // [ 0x43, 0x44, 0x30, 0x30, 0x31 ], // iso (cd/dvd) // ]; // those ones are now removed because of the extension filterings // static SIGNATURES_6: [[u8;6];4] = [ // [ 0x52, 0x61, 0x72, 0x21, 0x1A, 0x07 ], // rar // [ 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A ], // png // [ 0x21, 0x3C, 0x61, 0x72, 0x63, 0x68 ], // deb // [ 0x7B, 0x5C, 0x72, 0x74, 0x66, 0x31 ], // rtf // ]; /// return true when the first bytes of the file aren't polite or match one /// of the known binary signatures. /// Signatures are taken in https://en.wikipedia.org/wiki/List_of_file_signatures /// Some signatures are omitted from list because they would not go past the /// specific test of the first byte anyway. /// /// If you feel this list should maybe be changed, contact /// me on miaou or raise an issue. pub fn is_known_binary(bytes: &[u8]) -> bool { if bytes.len() < 4 { return false; } let c = bytes[0]; if c < 9 || (c > 13 && c < 32) || c >= 254 { // c < 9 include several signatures // 14 to 31 includes several signatures among them some variants of zip, gzip, etc. // FE is "þ", FF is "ÿ" // the FE/FF cases includes several signatures like Mach-O, jpeg or mpeg // TODO Some non ASCII UTF-8 chars start with FE or FF - check it's OK return true; } // for signature in &SIGNATURES_2 { // if signature == &bytes[0..2] { // return true; // } // } // for signature in &SIGNATURES_3 { // if signature == &bytes[0..3] { // return true; // } // } if SIGNATURES_4.contains(&bytes[0..4]) { return true; } // for signature in &SIGNATURES_5 { // if signature == &bytes[0..5] { // return true; // } // } // for signature in &SIGNATURES_6 { // if signature == &bytes[0..6] { // return true; // } // } false } /// Tell whether the file i pub fn is_file_known_binary>(path: P) -> io::Result { let mut buf = [0; 4]; let mut file = File::open(path)?; let n = file.read(&mut buf)?; Ok(is_known_binary(&buf[0..n])) } broot-1.46.3/src/content_type/mod.rs000064400000000000000000000015561046102023000155020ustar 00000000000000pub mod magic_numbers; pub mod extensions; use { std::{ io, path::Path, }, }; /// Assuming the path is already checked to be to a file /// (not a link or directory), tell whether it looks like a text file pub fn is_file_text>(path: P) -> io::Result { // the current algorithm is rather crude. If needed I'll add // more, like checking the start of the file is UTF8 compatible Ok(!is_file_binary(path)?) } /// Assuming the path is already checked to be to a file /// (not a link or directory), tell whether it looks like a binary file pub fn is_file_binary>(path: P) -> io::Result { if let Some(ext) = path.as_ref().extension().and_then(|s| s.to_str()) { if extensions::is_known_binary(ext) { return Ok(true); } } magic_numbers::is_file_known_binary(path) } broot-1.46.3/src/display/areas.rs000064400000000000000000000176351046102023000147550ustar 00000000000000use { super::*, crate::app::Panel, termimad::Area, }; /// the areas of the various parts of a panel. It's /// also where a state usually checks how many panels /// there are, and their respective positions #[derive(Debug, Clone)] pub struct Areas { pub state: Area, pub status: Area, pub input: Area, pub purpose: Option, pub pos_idx: usize, // from left to right pub nb_pos: usize, // number of displayed panels } const MINIMAL_PANEL_HEIGHT: u16 = 4; const MINIMAL_PANEL_WIDTH: u16 = 8; const MINIMAL_SCREEN_WIDTH: u16 = 16; enum Slot<'a> { Panel(usize), New(&'a mut Areas), } impl Areas { /// compute an area for a new panel which will be inserted pub fn create( present_panels: &mut [Panel], layout_instructions: &LayoutInstructions, mut insertion_idx: usize, screen: Screen, with_preview: bool, // slightly larger last panel ) -> Self { if insertion_idx > present_panels.len() { insertion_idx = present_panels.len(); } let mut areas = Areas { state: Area::uninitialized(), status: Area::uninitialized(), input: Area::uninitialized(), purpose: None, pos_idx: 0, nb_pos: 1, }; let mut slots = Vec::with_capacity(present_panels.len() + 1); for i in 0..insertion_idx { slots.push(Slot::Panel(i)); } slots.push(Slot::New(&mut areas)); for i in insertion_idx..present_panels.len() { slots.push(Slot::Panel(i)); } Self::compute_areas( present_panels, layout_instructions, &mut slots, screen, with_preview, ); areas } pub fn resize_all( panels: &mut [Panel], layout_instructions: &LayoutInstructions, screen: Screen, with_preview: bool, // slightly larger last panel ) { let mut slots = Vec::new(); for i in 0..panels.len() { slots.push(Slot::Panel(i)); } Self::compute_areas( panels, layout_instructions, &mut slots, screen, with_preview, ) } fn compute_areas( panels: &mut [Panel], layout_instructions: &LayoutInstructions, slots: &mut [Slot], screen: Screen, with_preview: bool, // slightly larger last panel ) { let screen_height = screen.height.max(MINIMAL_PANEL_HEIGHT); let screen_width = screen.width.max(MINIMAL_SCREEN_WIDTH); let n = slots.len() as u16; // compute auto/default panel widths let mut panel_width = if with_preview { 3 * screen_width / (3 * n + 1) } else { screen_width / n }; if panel_width < MINIMAL_PANEL_WIDTH { panel_width = panel_width.max(MINIMAL_PANEL_WIDTH); } let nb_pos = slots.len(); let mut panel_widths = vec![panel_width; nb_pos]; panel_widths[nb_pos - 1] = screen_width - (nb_pos as u16 - 1) * panel_width; // adjust panel widths with layout instructions if nb_pos > 1 { for instruction in &layout_instructions.instructions { debug!("Applying {:?}", instruction); debug!("panel_widths before: {:?}", &panel_widths); match *instruction { LayoutInstruction::Clear => {} // not supposed to happen LayoutInstruction::MoveDivider { divider, dx } => { if divider + 1 >= nb_pos { continue; } let (decr, incr, diff) = if dx < 0 { (divider, divider + 1, (-dx) as u16) } else { (divider + 1, divider, dx as u16) }; let diff = diff.min(panel_widths[decr] - MINIMAL_PANEL_WIDTH); panel_widths[decr] -= diff; panel_widths[incr] += diff; } LayoutInstruction::SetPanelWidth { panel, width } => { if panel >= nb_pos { continue; } let width = width.max(MINIMAL_PANEL_WIDTH); if width > panel_widths[panel] { let mut diff = width - panel_widths[panel]; // as we try to increase the width of 'panel' we have to decrease the // widths of the other ones while diff > 0 { let mut freed = 0; let step = diff / (nb_pos as u16 - 1); for i in 0..nb_pos { if i != panel { let step = step.min(panel_widths[i] - MINIMAL_PANEL_WIDTH); panel_widths[i] -= step; freed += step; } } if freed == 0 { break; } diff -= freed; panel_widths[panel] += freed; } } else { // we distribute the freed width among other panels let freed = panel_widths[panel] - width; panel_widths[panel] = width; let step = freed / (nb_pos as u16 - 1); for i in 0..nb_pos { if i != panel { panel_widths[i] += step; } } let rem = freed - (nb_pos as u16 - 1) * freed; for i in 0..nb_pos { if i != panel { panel_widths[i] += rem; break; } } } } } debug!("panel_widths after: {:?}", &panel_widths); } } // compute the areas of each slot, and give it to their panels let mut x = 0; #[allow(clippy::needless_range_loop)] for slot_idx in 0..nb_pos { let panel_width = panel_widths[slot_idx]; let areas: &mut Areas = match &mut slots[slot_idx] { Slot::Panel(panel_idx) => &mut panels[*panel_idx].areas, Slot::New(areas) => areas, }; let y = screen_height - 2; areas.state = Area::new(x, 0, panel_width, y); areas.status = if WIDE_STATUS { Area::new(0, y, screen_width, 1) } else { Area::new(x, y, panel_width, 1) }; let y = y + 1; areas.input = Area::new(x, y, panel_width, 1); if slot_idx == nb_pos - 1 { // the char at the bottom right of the terminal should not be touched // (it makes some terminals flicker) so the input area is one char shorter areas.input.width -= 1; } areas.purpose = if slot_idx > 0 { // the purpose area is over the panel at left let area_width = panel_widths[slot_idx - 1] / 2; Some(Area::new(x - area_width, y, area_width, 1)) } else { None }; areas.pos_idx = slot_idx; areas.nb_pos = nb_pos; x += panel_width; } } pub fn is_first(&self) -> bool { self.pos_idx == 0 } pub fn is_last(&self) -> bool { self.pos_idx + 1 == self.nb_pos } } broot-1.46.3/src/display/cell_size.rs000064400000000000000000000032661046102023000156260ustar 00000000000000 /// find and return the size of a cell (a char location) in pixels /// as (width, height). /// Many terminals don't fill this information correctly, so an /// error is expected (it works on kitty, where I use the data /// to compute the rendering dimensions of images) #[cfg(unix)] pub fn cell_size_in_pixels() -> std::io::Result<(u32, u32)> { use { libc::{ c_ushort, ioctl, STDOUT_FILENO, TIOCGWINSZ, }, std::io, }; // see http://www.delorie.com/djgpp/doc/libc/libc_495.html #[repr(C)] struct winsize { ws_row: c_ushort, /* rows, in characters */ ws_col: c_ushort, /* columns, in characters */ ws_xpixel: c_ushort, /* horizontal size, pixels */ ws_ypixel: c_ushort, /* vertical size, pixels */ } let mut w = winsize { ws_row: 0, ws_col: 0, ws_xpixel: 0, ws_ypixel: 0, }; #[allow(clippy::useless_conversion)] let r = unsafe { ioctl(STDOUT_FILENO, TIOCGWINSZ.into(), &mut w) }; if r == 0 && w.ws_xpixel > w.ws_col && w.ws_ypixel > w.ws_row { Ok(( (w.ws_xpixel / w.ws_col) as u32, (w.ws_ypixel / w.ws_row) as u32, )) } else { warn!("failed to fetch cell dimension with ioctl"); Err(io::Error::new( io::ErrorKind::Other, "failed to fetch terminal dimension with ioctl", )) } } #[cfg(not(unix))] pub fn cell_size_in_pixels() -> std::io::Result<(u32, u32)> { // there's probably a way but I don't know it Err(std::io::Error::new( std::io::ErrorKind::Other, "fetching cell size isn't supported on Windows", )) } broot-1.46.3/src/display/col.rs000064400000000000000000000117741046102023000144350ustar 00000000000000use { crate::{ app::AppState, errors::ConfError, tree::Tree, }, serde::Deserialize, std::{ convert::TryFrom, str::FromStr, }, }; // number of columns in enum const COLS_COUNT: usize = 10; /// One of the "columns" of the tree view #[derive(Debug, Clone, Copy, PartialEq)] pub enum Col { /// selection mark, typically a triangle on the selected line Mark, /// Git file status Git, /// the branch showing filliation Branch, /// The filesystem's device id (unix only) DeviceId, /// file mode and ownership Permission, /// last modified date Date, /// file size, including size bar in sort_by_size mode Size, /// number of files in the directory Count, /// marks whether the path is staged (not used for now, may be removed) Staged, /// name of the file, or subpath if relevant due to filtering mode Name, } pub type Cols = [Col; COLS_COUNT]; #[derive(Debug, Clone, Deserialize)] #[serde(untagged)] pub enum ColsConf { /// the old representation, with one character per column Compact(String), /// the newer representation, with column names in clear Array(Vec), } /// Default column order pub static DEFAULT_COLS: Cols = [ Col::Mark, Col::Git, Col::DeviceId, Col::Size, Col::Date, Col::Permission, Col::Count, Col::Branch, Col::Staged, Col::Name, ]; impl FromStr for Col { type Err = ConfError; fn from_str(s: &str) -> Result { let s = s.to_lowercase(); match s.as_ref() { "m" | "mark" => Ok(Self::Mark), "g" | "git" => Ok(Self::Git), "dev" | "device" | "device-id" => Ok(Self::DeviceId), "b" | "branch" => Ok(Self::Branch), "p" | "permission" => Ok(Self::Permission), "d" | "date" => Ok(Self::Date), "s" | "size" => Ok(Self::Size), "c" | "count" => Ok(Self::Count), "staged" => Ok(Self::Staged), "n" | "name" => Ok(Self::Name), _ => Err(ConfError::InvalidCols { details: format!("column not recognized : {s}"), }), } } } impl Col { /// return the index of the column among the complete Cols ordered list pub fn index_in(self, cols: &Cols) -> Option { for (idx, col) in cols.iter().enumerate() { if *col == self { return Some(idx); } } None } /// tell whether this column should have an empty character left pub fn needs_left_margin(self) -> bool { match self { Col::Mark => false, Col::Git => false, Col::DeviceId => true, Col::Size => true, Col::Date => true, Col::Permission => true, Col::Count => false, Col::Branch => false, Col::Staged => false, Col::Name => false, } } pub fn is_visible( self, tree: &Tree, _app_state: Option<&AppState>, ) -> bool { let tree_options = &tree.options; match self { Col::Mark => tree_options.show_selection_mark, Col::Git => tree.git_status.is_some(), Col::DeviceId => tree_options.show_device_id, Col::Size => tree_options.show_sizes, Col::Date => tree_options.show_dates, Col::Permission => tree_options.show_permissions, Col::Count => tree_options.show_counts, Col::Branch => true, //Col::Staged => app_state.map_or(false, |a| !a.stage.is_empty()), Col::Staged => false, Col::Name => true, } } } impl TryFrom<&ColsConf> for Cols { type Error = ConfError; fn try_from(cc: &ColsConf) -> Result { match cc { ColsConf::Compact(s) => parse_cols_single_str(s), ColsConf::Array(arr) => parse_cols(arr), } } } /// return a Cols which tries to take the s setting into account /// but is guaranteed to have every Col exactly once. #[allow(clippy::ptr_arg)] // &[String] won't compile on all platforms pub fn parse_cols(arr: &Vec) -> Result { let mut cols = DEFAULT_COLS; for (idx, s) in arr.iter().enumerate() { if idx >= COLS_COUNT { return Err(ConfError::InvalidCols { details: format!("too long: {arr:?}"), }); } // we swap the cols, to ensure both keeps being present let col = Col::from_str(s)?; let dest_idx = col.index_in(&cols).unwrap(); // can't be none by construct cols[dest_idx] = cols[idx]; cols[idx] = col; } debug!("cols from conf = {:?}", cols); Ok(cols) } /// return a Cols which tries to take the s setting into account /// but is guaranteed to have every Col exactly once. pub fn parse_cols_single_str(s: &str) -> Result { parse_cols(&s.chars().map(String::from).collect()) } broot-1.46.3/src/display/displayable_tree.rs000064400000000000000000000533571046102023000171730ustar 00000000000000use { super::{ cond_bg, Col, CropWriter, GitStatusDisplay, MatchedString, num_format::format_count, SPACE_FILLING, BRANCH_FILLING, }, crate::{ app::AppState, content_search::ContentMatch, errors::ProgramError, file_sum::FileSum, pattern::PatternObject, skin::{ExtColorMap, StyleMap}, task_sync::ComputationResult, tree::{Tree, TreeLine, TreeLineType}, }, chrono::{DateTime, Local, LocalResult, TimeZone}, crokey::crossterm::{ cursor, QueueableCommand, }, file_size, git2::Status, std::io::Write, termimad::{CompoundStyle, ProgressBar}, }; /// A tree wrapper which can be used either /// - to write on the screen in the application, /// - or to write in a file or an exported string. /// /// Using it in the application (with in_app true) means that /// - the selection is drawn /// - a scrollbar may be drawn /// - the empty lines will be erased pub struct DisplayableTree<'a, 's, 't> { pub app_state: Option<&'a AppState>, pub tree: &'t Tree, pub skin: &'s StyleMap, pub area: termimad::Area, pub in_app: bool, // if true we show the selection and scrollbar pub ext_colors: &'s ExtColorMap, } impl<'a, 's, 't> DisplayableTree<'a, 's, 't> { pub fn out_of_app( tree: &'t Tree, skin: &'s StyleMap, ext_colors: &'s ExtColorMap, width: u16, height: u16, ) -> DisplayableTree<'a, 's, 't> { DisplayableTree { app_state: None, tree, skin, ext_colors, area: termimad::Area { left: 0, top: 0, width, height, }, in_app: false, } } fn label_style( &self, line: &TreeLine, selected: bool, ) -> CompoundStyle { let style = match &line.line_type { TreeLineType::Dir => &self.skin.directory, TreeLineType::File => { if line.is_exe() { &self.skin.exe } else { &self.skin.file } } TreeLineType::BrokenSymLink(_) | TreeLineType::SymLink { .. } => &self.skin.link, TreeLineType::Pruning => &self.skin.pruning, }; let mut style = style.clone(); if let Some(ext_color) = line.extension().and_then(|ext| self.ext_colors.get(ext)) { style.set_fg(ext_color); } if selected { if let Some(c) = self.skin.selected_line.get_bg() { style.set_bg(c); } } style } fn write_line_count( &self, cw: &mut CropWriter, line: &TreeLine, count_len: usize, selected: bool, ) -> Result { Ok(if let Some(s) = line.sum { cond_bg!(count_style, self, selected, self.skin.count); let s = format_count(s.to_count()); cw.queue_g_string(count_style, format!("{s:>count_len$}"))?; 1 } else { count_len + 1 }) } #[cfg(unix)] fn write_line_device_id( &self, cw: &mut CropWriter, line: &TreeLine, selected: bool, ) -> Result { let device_id = line.device_id(); cond_bg!(style, self, selected, self.skin.device_id_major); cw.queue_g_string(style, format!("{:>3}", device_id.major))?; cond_bg!(style, self, selected, self.skin.device_id_sep); cw.queue_char(style, ':')?; cond_bg!(style, self, selected, self.skin.device_id_minor); cw.queue_g_string(style, format!("{:<3}", device_id.minor))?; Ok(0) } fn write_line_selection_mark( &self, cw: &mut CropWriter, style: &CompoundStyle, selected: bool, ) -> Result { Ok(if selected { cw.queue_char(style, '▶')?; 0 } else { 1 }) } fn write_line_size( &self, cw: &mut CropWriter, line: &TreeLine, style: &CompoundStyle, _selected: bool, ) -> Result { Ok(if let Some(s) = line.sum { cw.queue_g_string( style, format!("{:>4}", file_size::fit_4(s.to_size())), )?; 1 } else { 5 }) } /// only makes sense when there's only one level /// (so in sort mode) fn write_line_size_with_bar( &self, cw: &mut CropWriter, line: &TreeLine, label_style: &CompoundStyle, total_size: FileSum, selected: bool, ) -> Result { Ok(if let Some(s) = line.sum { let pb = ProgressBar::new(s.part_of_size(total_size), 10); cond_bg!(sparse_style, self, selected, self.skin.sparse); cw.queue_g_string( label_style, format!("{:>4}", file_size::fit_4(s.to_size())), )?; cw.queue_char( sparse_style, if s.is_sparse() && line.is_file() { 's' } else { ' ' }, )?; cw.queue_g_string(label_style, format!("{pb:<10}"))?; 1 } else { 16 }) } fn write_line_git_status( &self, cw: &mut CropWriter, line: &TreeLine, selected: bool, ) -> Result { let (style, char) = if !line.is_selectable() { (&self.skin.tree, ' ') } else { match line.git_status.map(|s| s.status) { Some(Status::CURRENT) => (&self.skin.git_status_current, ' '), Some(Status::WT_NEW) => (&self.skin.git_status_new, 'N'), Some(Status::CONFLICTED) => (&self.skin.git_status_conflicted, 'C'), Some(Status::WT_MODIFIED) => (&self.skin.git_status_modified, 'M'), Some(Status::IGNORED) => (&self.skin.git_status_ignored, 'I'), None => (&self.skin.tree, ' '), _ => (&self.skin.git_status_other, '?'), } }; cond_bg!(git_style, self, selected, style); cw.queue_char(git_style, char)?; Ok(0) } fn write_date( &self, cw: &mut CropWriter, seconds: i64, selected: bool, ) -> Result { if let LocalResult::Single(date_time) = Local.timestamp_opt(seconds, 0) { cond_bg!(date_style, self, selected, self.skin.dates); cw.queue_g_string( date_style, date_time .format(self.tree.options.date_time_format) .to_string(), )?; } Ok(1) } fn write_branch( &self, cw: &mut CropWriter, line_index: usize, line: &TreeLine, selected: bool, staged: bool, ) -> Result { cond_bg!(branch_style, self, selected, self.skin.tree); let mut branch = String::new(); for depth in 0..line.depth { branch.push_str( if line.left_branches[depth as usize] { if self.tree.has_branch(line_index + 1, depth as usize) { // TODO: If a theme is on, remove the horizontal lines if depth == line.depth - 1 { if staged { "├◍─" } else { "├──" } } else { "│ " } } else if staged { "└◍─" } else { "└──" } } else { " " }, ); } if !branch.is_empty() { cw.queue_g_string(branch_style, branch)?; } Ok(0) } /// write the symbol showing whether the path is staged fn write_line_stage_mark( &self, cw: &mut CropWriter, style: &CompoundStyle, staged: bool, ) -> Result { Ok(if staged { cw.queue_char(style, '◍')?; // ▣ 0 } else { 1 }) } /// write the name or subpath, depending on the pattern_object fn write_line_label( &self, cw: &mut CropWriter, line: &TreeLine, style: &CompoundStyle, pattern_object: PatternObject, selected: bool, ) -> Result { cond_bg!(char_match_style, self, selected, self.skin.char_match); if let Some(icon) = line.icon { cw.queue_char(style, icon)?; cw.queue_char(style, ' ')?; cw.queue_char(style, ' ')?; } if pattern_object.subpath { if self.tree.options.show_matching_characters_on_path_searches && line.unlisted == 0 { let name_match = self.tree.options.pattern.pattern .find_string(&line.subpath); let mut path_ms = MatchedString::new( name_match, &line.subpath, style, char_match_style, ); let name_ms = path_ms.split_on_last('/'); cond_bg!(parent_style, self, selected, self.skin.parent); if let Some(name_ms) = name_ms { path_ms.base_style = parent_style; let allowed = cw.allowed - 2.min(cw.allowed); let tail_len = name_ms.width(); if tail_len < allowed { let path_width = path_ms.width(); if path_width > 1 && path_width + tail_len > allowed + 1 { cw.queue_char(style, '…')?; path_ms.cut_left_to_fit(allowed - tail_len - 1); } path_ms.queue_on(cw)?; } name_ms.queue_on(cw)?; } else { path_ms.queue_on(cw)?; } } else { cw.queue_str(style, &line.name)?; } } else { let name_match = self.tree.options.pattern.pattern .find_string(&line.name); let matched_string = MatchedString::new( name_match, &line.name, style, char_match_style, ); matched_string.queue_on(cw)?; } match &line.line_type { TreeLineType::Dir => { if line.unlisted > 0 { cw.queue_str(style, " …")?; } } TreeLineType::BrokenSymLink(direct_path) => { cw.queue_str(style, " -> ")?; cond_bg!(error_style, self, selected, self.skin.file_error); cw.queue_str(error_style, direct_path)?; } TreeLineType::SymLink { final_is_dir, direct_target, .. } => { cw.queue_str(style, " -> ")?; let target_style = if *final_is_dir { &self.skin.directory } else { &self.skin.file }; cond_bg!(target_style, self, selected, target_style); cw.queue_str(target_style, direct_target)?; } _ => {} } Ok(1) } fn write_content_extract( &self, cw: &mut CropWriter, extract: ContentMatch, selected: bool, ) -> Result<(), ProgramError> { cond_bg!(extract_style, self, selected, self.skin.content_extract); cond_bg!(match_style, self, selected, self.skin.content_match); cw.queue_str(extract_style, " ")?; if extract.needle_start > 0 { cw.queue_str(extract_style, &extract.extract[0..extract.needle_start])?; } cw.queue_str( match_style, &extract.extract[extract.needle_start..extract.needle_end], )?; if extract.needle_end < extract.extract.len() { cw.queue_str(extract_style, &extract.extract[extract.needle_end..])?; } Ok(()) } pub fn write_root_line( &self, cw: &mut CropWriter, selected: bool, ) -> Result<(), ProgramError> { cond_bg!(style, self, selected, self.skin.directory); let line = &self.tree.lines[0]; if self.tree.options.show_sizes { if let Some(s) = line.sum { cw.queue_g_string( style, format!("{:>4} ", file_size::fit_4(s.to_size())), )?; } } let title = line.path.to_string_lossy(); cw.queue_str(style, &title)?; if self.in_app && !cw.is_full() { if let ComputationResult::Done(git_status) = &self.tree.git_status { let git_status_display = GitStatusDisplay::from( git_status, self.skin, cw.allowed, ); git_status_display.write(cw, selected)?; } #[cfg(unix)] if self.tree.options.show_root_fs { if let Some(mount) = line.mount() { let fs_space_display = crate::filesystems::MountSpaceDisplay::from( &mount, self.skin, cw.allowed, ); fs_space_display.write(cw, selected)?; } } self.extend_line_bg(cw, selected)?; } Ok(()) } /// if in app, extend the background till the end of screen row pub fn extend_line_bg( &self, cw: &mut CropWriter, selected: bool, ) -> Result<(), ProgramError> { if self.in_app && !cw.is_full() { let style = if selected { &self.skin.selected_line } else { &self.skin.default }; cw.fill(style, &SPACE_FILLING)?; } Ok(()) } /// write the whole tree on the given `W` pub fn write_on(&self, f: &mut W) -> Result<(), ProgramError> { #[cfg(not(any(target_family = "windows", target_os = "android")))] let perm_writer = super::PermWriter::for_tree(self.skin, self.tree); let tree = self.tree; let total_size = tree.total_sum(); let scrollbar = if self.in_app { termimad::compute_scrollbar( tree.scroll, tree.lines.len() - 1, // the root line isn't scrolled self.area.height - 1, // the scrollbar doesn't cover the first line self.area.top + 1, ) } else { None }; if self.in_app { f.queue(cursor::MoveTo(self.area.left, self.area.top))?; } let mut cw = CropWriter::new(f, self.area.width as usize); let pattern_object = tree.options.pattern.pattern.object(); self.write_root_line(&mut cw, self.in_app && tree.selection == 0)?; self.skin.queue_reset(f)?; let visible_cols: Vec = tree .options .cols_order .iter() .filter(|col| col.is_visible(tree, self.app_state)) .cloned() .collect(); // if necessary we compute the width of the count column let count_len = if tree.options.show_counts { tree.lines.iter() .skip(1) // we don't show the counts of the root .map(|l| l.sum.map_or(0, |s| s.to_count())) .max() .map(|c| format_count(c).len()) .unwrap_or(0) } else { 0 }; // we compute the length of the dates, depending on the format let date_len = if tree.options.show_dates { let date_time: DateTime = Local::now(); date_time.format(tree.options.date_time_format).to_string().len() } else { 0 // we don't care }; for y in 1..self.area.height { if self.in_app { f.queue(cursor::MoveTo(self.area.left, y + self.area.top))?; } else { write!(f, "\r\n")?; } let mut line_index = y as usize; if line_index > 0 { line_index += tree.scroll; } let mut selected = false; let mut cw = CropWriter::new(f, self.area.width as usize); let cw = &mut cw; if line_index < tree.lines.len() { let line = &tree.lines[line_index]; selected = self.in_app && line_index == tree.selection; let label_style = self.label_style(line, selected); let mut in_branch = false; let space_style = if selected { &self.skin.selected_line } else { &self.skin.default }; if visible_cols[0].needs_left_margin() { cw.queue_char(space_style, ' ')?; } let staged = self.app_state .map_or(false, |a| a.stage.contains(&line.path)); for col in &visible_cols { let void_len = match col { Col::Mark => { self.write_line_selection_mark(cw, &label_style, selected)? } Col::Git => { self.write_line_git_status(cw, line, selected)? } Col::Branch => { in_branch = true; self.write_branch(cw, line_index, line, selected, staged)? } Col::DeviceId => { #[cfg(not(unix))] { 0 } #[cfg(unix)] self.write_line_device_id(cw, line, selected)? } Col::Permission => { #[cfg(any(target_family = "windows", target_os = "android"))] { 0 } #[cfg(not(any(target_family = "windows", target_os = "android")))] perm_writer.write_permissions(cw, line, selected)? } Col::Date => { if let Some(seconds) = line.sum.and_then(|sum| sum.to_valid_seconds()) { self.write_date(cw, seconds, selected)? } else { date_len + 1 } } Col::Size => { if tree.options.sort.prevent_deep_display() { // as soon as there's only one level displayed we can show the size bars self.write_line_size_with_bar(cw, line, &label_style, total_size, selected)? } else { self.write_line_size(cw, line, &label_style, selected)? } } Col::Count => { self.write_line_count(cw, line, count_len, selected)? } Col::Staged => { self.write_line_stage_mark(cw, &label_style, staged)? } Col::Name => { in_branch = false; self.write_line_label(cw, line, &label_style, pattern_object, selected)? } }; // void: intercol & replacing missing cells if in_branch && void_len > 2 { cond_bg!(void_style, self, selected, self.skin.tree); cw.repeat(void_style, &BRANCH_FILLING, void_len)?; } else { cond_bg!(void_style, self, selected, self.skin.default); cw.repeat(void_style, &SPACE_FILLING, void_len)?; } } if cw.allowed > 8 && pattern_object.content { let extract = tree.options.pattern.pattern .find_content(&line.path, cw.allowed - 2); if let Some(extract) = extract { self.write_content_extract(cw, extract, selected)?; } } } self.extend_line_bg(cw, selected)?; self.skin.queue_reset(f)?; if self.in_app { if let Some((sctop, scbottom)) = scrollbar { f.queue(cursor::MoveTo(self.area.left + self.area.width - 1, y))?; let style = if sctop <= y && y <= scbottom { &self.skin.scrollbar_thumb } else { &self.skin.scrollbar_track }; style.queue_str(f, "▐")?; } } } if !self.in_app { write!(f, "\r\n")?; } Ok(()) } } broot-1.46.3/src/display/flags_display.rs000064400000000000000000000014101046102023000164630ustar 00000000000000 use { super::W, crate::{ errors::ProgramError, flag::Flag, skin::PanelSkin, }, }; /// compute the needed length for displaying the flags pub fn visible_width(flags: &[Flag]) -> u16 { let mut width = flags.len() * 2 + 1; for flag in flags { width += flag.name.len(); // we assume only ascii chars width += flag.value.len(); } width as u16 } /// draw the flags pub fn write( w: &mut W, flags: &[Flag], panel_skin: &PanelSkin, ) -> Result<(), ProgramError> { for flag in flags { panel_skin.styles.flag_label.queue_str(w, format!( " {}:", flag.name))?; panel_skin.styles.flag_value.queue(w, flag.value)?; panel_skin.styles.flag_label.queue(w, ' ')?; } Ok(()) } broot-1.46.3/src/display/git_status_display.rs000064400000000000000000000046671046102023000175760ustar 00000000000000use { super::CropWriter, crate::{ display::cond_bg, errors::ProgramError, git::TreeGitStatus, skin::StyleMap, }, }; pub struct GitStatusDisplay<'a, 's> { status: &'a TreeGitStatus, skin: &'s StyleMap, show_branch: bool, show_wide: bool, show_stats: bool, pub width: usize, } impl<'a, 's> GitStatusDisplay<'a, 's> { pub fn from(status: &'a TreeGitStatus, skin: &'s StyleMap, available_width: usize) -> Self { let mut show_branch = false; let mut width = 0; if let Some(branch) = &status.current_branch_name { let branch_width = branch.chars().count(); if branch_width < available_width { width += branch_width; show_branch = true; } } let mut show_stats = false; let unstyled_stats = format!("+{}-{}", status.insertions, status.deletions); let stats_width = unstyled_stats.len(); if width + stats_width < available_width { width += stats_width; show_stats = true; } let show_wide = width + 3 < available_width; if show_wide { width += 3; // difference between compact and wide format widths } Self { status, skin, show_branch, show_wide, show_stats, width, } } pub fn write( &self, cw: &mut CropWriter, selected: bool, ) -> Result<(), ProgramError> where W: std::io::Write, { if self.show_branch { cond_bg!(branch_style, self, selected, self.skin.git_branch); if let Some(name) = &self.status.current_branch_name { if self.show_wide { cw.queue_str(branch_style, " ᚜ ")?; } else { cw.queue_char(branch_style, ' ')?; } cw.queue_str(branch_style, name)?; cw.queue_char(branch_style, ' ')?; } } if self.show_stats { cond_bg!(insertions_style, self, selected, self.skin.git_insertions); cw.queue_g_string(insertions_style, format!("+{}", self.status.insertions))?; cond_bg!(deletions_style, self, selected, self.skin.git_deletions); cw.queue_g_string(deletions_style, format!("-{}", self.status.deletions))?; } Ok(()) } } broot-1.46.3/src/display/layout_instructions.rs000064400000000000000000000060271046102023000200140ustar 00000000000000use { lazy_regex::*, serde::Deserialize, std::str::FromStr, }; #[derive(Debug, Clone, Default, Deserialize)] #[serde(transparent)] pub struct LayoutInstructions { pub instructions: Vec, } #[derive(Debug, Clone, Copy, Deserialize)] #[serde(untagged)] pub enum LayoutInstruction { Clear, // clear all instructions MoveDivider { divider: usize, dx: i16 }, SetPanelWidth { panel: usize, width: u16 }, } /// arguments for moving a divider, read from a string eg "0 -5" /// (move the first divider 5 cells to the left) #[derive(Debug, Clone, Copy)] pub struct MoveDividerArgs { pub divider: usize, pub dx: i16, } impl FromStr for MoveDividerArgs { type Err = &'static str; fn from_str(s: &str) -> Result { if let Some((_, divider, dx)) = regex_captures!(r"^\s*(\d)\s+(-?\d{1,3})\s*$", s) { Ok(Self { divider: divider.parse().unwrap(), dx: dx.parse().unwrap(), }) } else { Err("not the expected move_divider args") } } } /// arguments for setting the width of a panel, read from a string eg "1 150" #[derive(Debug, Clone, Copy)] pub struct SetPanelWidthArgs { pub panel: usize, pub width: u16, } impl FromStr for SetPanelWidthArgs { type Err = &'static str; fn from_str(s: &str) -> Result { if let Some((_, panel, width)) = regex_captures!(r"^\s*(\d)\s+(\d{1,4})\s*$", s) { Ok(Self { panel: panel.parse().unwrap(), width: width.parse().unwrap(), }) } else { Err("not the expected set_panel_width args") } } } impl LayoutInstruction { pub fn is_moving_divider( self, idx: usize, ) -> bool { match self { Self::MoveDivider { divider, .. } => divider == idx, _ => false, } } } impl LayoutInstructions { pub fn push( &mut self, new_instruction: LayoutInstruction, ) { use LayoutInstruction::*; match new_instruction { Clear => { self.instructions.clear(); } SetPanelWidth { panel: new_panel, .. } => { // all previous SetPanelWidth for the same panel are now irrelevant self.instructions.retain(|i| match i { SetPanelWidth { panel, .. } => *panel != new_panel, _ => true, }); } MoveDivider { divider: new_divider, dx: new_dx, } => { // if the last instruction is a move of the same divider, we adjust it if let Some(MoveDivider { divider, dx }) = self.instructions.last_mut() { if *divider == new_divider { *dx += new_dx; return; } } } } self.instructions.push(new_instruction); } } broot-1.46.3/src/display/luma.rs000064400000000000000000000025261046102023000146110ustar 00000000000000pub use { crokey::crossterm::tty::IsTty, once_cell::sync::Lazy, serde::Deserialize, }; #[derive(Debug, Clone, Copy, Deserialize, PartialEq)] #[serde(rename_all = "lowercase")] pub enum Luma { Light, Unknown, Dark, } /// Return the light of the terminal background, which is a value /// between 0 (black) and 1 (white). pub fn luma() -> &'static Result { static LUMA: Lazy> = Lazy::new(|| { let luma = time!(Debug, terminal_light::luma()); info!("terminal's luma: {:?}", &luma); luma }); &LUMA } impl Luma { pub fn read() -> Self { match luma() { Ok(luma) if *luma > 0.6 => Self::Light, Ok(_) => Self::Dark, _ => Self::Unknown, } } } #[derive(Clone, Debug, Deserialize)] #[serde(untagged)] pub enum LumaCondition { Simple(Luma), Array(Vec), } impl LumaCondition { pub fn is_verified(&self) -> bool { let luma = if std::io::stdout().is_tty() { Luma::read() } else { Luma::Unknown }; self.includes(luma) } pub fn includes(&self, other: Luma) -> bool { match self { Self::Simple(luma) => other == *luma, Self::Array(arr) => arr.contains(&other), } } } broot-1.46.3/src/display/matched_string.rs000064400000000000000000000133571046102023000166520ustar 00000000000000use { super::{CropWriter, SPACE_FILLING}, crate::pattern::NameMatch, termimad::{ minimad::Alignment, CompoundStyle, StrFit, }, unicode_width::{UnicodeWidthChar, UnicodeWidthStr}, }; pub struct MatchedString<'a> { pub name_match: Option, pub string: &'a str, pub base_style: &'a CompoundStyle, pub match_style: &'a CompoundStyle, pub display_width: Option, pub align: Alignment, } impl<'a> MatchedString<'a> { pub fn new( name_match: Option, string: &'a str, base_style: &'a CompoundStyle, match_style: &'a CompoundStyle, ) -> Self { Self { name_match, string, base_style, match_style, display_width: None, align: Alignment::Left, } } /// If the string contains sep, then cut the tail of this matched /// string and return it. /// Note: a non none display_width currently prevents splitting /// (i.e. it's not yet implemented and would involve compute width) pub fn split_on_last(&mut self, sep: char) -> Option { if self.display_width.is_some() { // the proper algo would need measuring the left part I guess None } else { self.string .rfind(sep) .map(|sep_idx| { let right = &self.string[sep_idx+1..]; self.string = &self.string[..sep_idx+1]; let left_chars_count = self.string.chars().count(); let right_name_match = self.name_match.as_mut() .map(|nm| nm.cut_after(left_chars_count)); MatchedString { name_match: right_name_match, string: right, base_style: self.base_style, match_style: self.match_style, display_width: None, align: self.align, } }) } } pub fn fill(&mut self, width: usize, align: Alignment) { self.display_width = Some(width); self.align = align; } pub fn width(&self) -> usize { UnicodeWidthStr::width(self.string) } /// Remove characters left so that the visible width is equal or /// less to the required width pub fn cut_left_to_fit(&mut self, max_width: usize) -> usize { let mut removed_char_count = 0; let mut break_idx = 0; let mut width = self.width(); for (idx, c) in self.string.char_indices() { if width <= max_width { break; } break_idx = idx + c.len_utf8(); let char_width = c.width().unwrap_or(0); if char_width > width { warn!("inconsistent char/str widths"); break; } width -= char_width; removed_char_count += 1; } if removed_char_count > 0 { self.string = &self.string[break_idx..]; self.name_match = self.name_match .take() .map(|mut nm| nm.cut_after(removed_char_count-1)); } removed_char_count } pub fn queue_on(&self, cw: &mut CropWriter<'_, W>) -> Result<(), termimad::Error> where W: std::io::Write, { if let Some(m) = &self.name_match { let mut pos_idx: usize = 0; let mut combined_style = self.base_style.clone(); combined_style.overwrite_with(self.match_style); let mut right_filling = 0; let mut s = self.string; if let Some(dw) = self.display_width { let w = unicode_width::UnicodeWidthStr::width(s); #[allow(clippy::comparison_chain)] if w > dw { let (count_bytes, _) = StrFit::count_fitting(s, dw); s = &s[0..count_bytes]; } else if w < dw { match self.align { Alignment::Right => { cw.repeat(self.base_style, &SPACE_FILLING, dw - w)?; } Alignment::Center => { right_filling = (dw - w) / 2; cw.repeat(self.base_style, &SPACE_FILLING, dw - w - right_filling)?; } _ => { right_filling = dw - w; } } } } // we might call queue_char more than allowed but that's okay // because the cropwriter will crop them for (cand_idx, cand_char) in s.chars().enumerate() { if pos_idx < m.pos.len() && m.pos[pos_idx] == cand_idx { cw.queue_char(&combined_style, cand_char)?; pos_idx += 1; } else { cw.queue_char(self.base_style, cand_char)?; } } if right_filling > 0 { cw.repeat(self.base_style, &SPACE_FILLING, right_filling)?; } } else if let Some(w) = self.display_width { match self.align { Alignment::Center => { cw.queue_str(self.base_style, &format!("{:^w$}", self.string, w = w))?; } Alignment::Right => { cw.queue_str(self.base_style, &format!("{:>w$}", self.string, w = w))?; } _ => { cw.queue_str(self.base_style, &format!("{: { let mut cloned_style; let $dst = if $selected { cloned_style = $src.clone(); if let Some(c) = $self.skin.selected_line.get_bg() { cloned_style.set_bg(c); } &cloned_style } else { &$src }; }; } mod areas; mod cell_size; mod col; mod displayable_tree; pub mod flags_display; mod git_status_display; mod layout_instructions; mod luma; mod matched_string; mod num_format; mod screen; pub mod status_line; #[cfg(not(any(target_family="windows",target_os="android")))] mod permissions; pub use { areas::Areas, col::*, cond_bg, displayable_tree::DisplayableTree, git_status_display::GitStatusDisplay, layout_instructions::*, luma::LumaCondition, matched_string::MatchedString, screen::Screen, cell_size::*, }; use { once_cell::sync::Lazy, termimad::*, }; #[cfg(not(any(target_family="windows",target_os="android")))] pub use { permissions::PermWriter, }; pub static BRANCH_FILLING: Lazy = Lazy::new(|| { Filling::from_char('─') }); /// if true then the status of a panel covers the whole width /// of the terminal (over the other panels) pub const WIDE_STATUS: bool = true; /// the type used by all GUI writing functions pub type W = std::io::BufWriter; /// return the writer used by the application pub fn writer() -> W { std::io::BufWriter::new(std::io::stderr()) } broot-1.46.3/src/display/num_format.rs000064400000000000000000000013321046102023000160140ustar 00000000000000 /// Format a number with commas as thousands separators pub fn format_count(count: usize) -> String { let mut s = count.to_string(); let l = s.len(); for i in 1..l { if i % 3 == 0 { s.insert(l-i, ','); } } s } #[test] fn test_format_count() { assert_eq!(&format_count(1), "1"); assert_eq!(&format_count(12), "12"); assert_eq!(&format_count(123), "123"); assert_eq!(&format_count(1234), "1,234"); assert_eq!(&format_count(12345), "12,345"); assert_eq!(&format_count(123456), "123,456"); assert_eq!(&format_count(1234567), "1,234,567"); assert_eq!(&format_count(12345678), "12,345,678"); assert_eq!(&format_count(1234567890), "1,234,567,890"); } broot-1.46.3/src/display/permissions.rs000064400000000000000000000105411046102023000162220ustar 00000000000000use { super::CropWriter, crate::{ display::cond_bg, errors::ProgramError, permissions, skin::StyleMap, tree::{Tree, TreeLine}, }, std::{io::Write, os::unix::fs::MetadataExt}, umask::*, }; /// an object which writes file permissions (mode, owner, group) pub struct PermWriter<'s> { pub skin: &'s StyleMap, max_user_len: usize, max_group_len: usize, } impl<'s> PermWriter<'s> { pub fn new( skin: &'s StyleMap, max_user_len: usize, max_group_len: usize, ) -> Self { Self { skin, max_user_len, max_group_len, } } pub fn for_tree( skin: &'s StyleMap, tree: &Tree, ) -> Self { let (max_user_len, max_group_len) = user_group_max_lengths(tree); Self::new(skin, max_user_len, max_group_len) } fn write_mode( &self, cw: &mut CropWriter, mode: Mode, selected: bool, ) -> Result<(), termimad::Error> { cond_bg!(n_style, self, selected, self.skin.perm__); cond_bg!(r_style, self, selected, self.skin.perm_r); cond_bg!(w_style, self, selected, self.skin.perm_w); cond_bg!(x_style, self, selected, self.skin.perm_x); if mode.has(USER_READ) { cw.queue_char(r_style, 'r')?; } else { cw.queue_char(n_style, '_')?; } if mode.has(USER_WRITE) { cw.queue_char(w_style, 'w')?; } else { cw.queue_char(n_style, '_')?; } if mode.has(USER_EXEC) { cw.queue_char(x_style, if mode.has_extra(SETUID) { 's' } else { 'x' })?; } else { cw.queue_char(n_style, if mode.has_extra(SETUID) { 'S' } else { '_' })?; } if mode.has(GROUP_READ) { cw.queue_char(r_style, 'r')?; } else { cw.queue_char(n_style, '_')?; } if mode.has(GROUP_WRITE) { cw.queue_char(w_style, 'w')?; } else { cw.queue_char(n_style, '_')?; } if mode.has(GROUP_EXEC) { cw.queue_char(x_style, if mode.has_extra(SETGID) { 's' } else { 'x' })?; } else { cw.queue_char(n_style, if mode.has_extra(SETGID) { 'S' } else { '_' })?; } if mode.has(OTHERS_READ) { cw.queue_char(r_style, 'r')?; } else { cw.queue_char(n_style, '_')?; } if mode.has(OTHERS_WRITE) { cw.queue_char(w_style, 'w')?; } else { cw.queue_char(n_style, '_')?; } if mode.has(OTHERS_EXEC) { cw.queue_char(x_style, if mode.has_extra(STICKY) { 't' } else { 'x' })?; } else { cw.queue_char(n_style, if mode.has_extra(STICKY) { 'T' } else { '_' })?; } Ok(()) } #[cfg(not(any(target_family = "windows", target_os = "android")))] pub fn write_permissions( &self, cw: &mut CropWriter, line: &TreeLine, selected: bool, ) -> Result { Ok(if line.is_selectable() { self.write_mode(cw, line.mode(), selected)?; let owner = permissions::user_name(line.metadata.uid()); cond_bg!(owner_style, self, selected, self.skin.owner); cw.queue_g_string( owner_style, format!(" {:w$}", &owner, w = self.max_user_len), )?; let group = permissions::group_name(line.metadata.gid()); cond_bg!(group_style, self, selected, self.skin.group); cw.queue_g_string( group_style, format!(" {:w$}", &group, w = self.max_group_len), )?; 1 } else { 9 + 1 + self.max_user_len + 1 + self.max_group_len + 1 }) } } fn user_group_max_lengths(tree: &Tree) -> (usize, usize) { let mut max_user_len = 0; let mut max_group_len = 0; if tree.options.show_permissions { for i in 1..tree.lines.len() { let line = &tree.lines[i]; let user = permissions::user_name(line.metadata.uid()); max_user_len = max_user_len.max(user.len()); let group = permissions::group_name(line.metadata.gid()); max_group_len = max_group_len.max(group.len()); } } (max_user_len, max_group_len) } broot-1.46.3/src/display/screen.rs000064400000000000000000000042551046102023000151330ustar 00000000000000use { super::W, crate::{ app::AppContext, errors::ProgramError, skin::PanelSkin, }, crokey::crossterm::{ cursor, terminal::{Clear, ClearType}, QueueableCommand, }, termimad::Area, }; /// The dimensions of the screen #[derive(Clone, Copy)] pub struct Screen { pub width: u16, pub height: u16, } impl Screen { pub fn new(con: &AppContext) -> Result { let mut screen = Screen { width: 0, height: 0, }; screen.read_size(con)?; Ok(screen) } pub fn set_terminal_size(&mut self, w: u16, h: u16, con: &AppContext) { self.width = w; self.height = h; if let Some(h) = con.launch_args.height { self.height = h; } } pub fn read_size(&mut self, con: &AppContext) -> Result<(), ProgramError> { let (w, h) = termimad::terminal_size(); self.set_terminal_size(w, h, con); Ok(()) } /// move the cursor to x,y pub fn goto(self, w: &mut W, x: u16, y: u16) -> Result<(), ProgramError> { w.queue(cursor::MoveTo(x, y))?; Ok(()) } /// clear from the cursor to the end of line pub fn clear_line(self, w: &mut W) -> Result<(), ProgramError> { w.queue(Clear(ClearType::UntilNewLine))?; Ok(()) } /// clear the area and everything to the right. /// Should be used with parcimony as it could lead to flickering. pub fn clear_area_to_right(self, w: &mut W, area: &Area) -> Result<(), ProgramError> { for y in area.top..area.top + area.height { self.goto(w, area.left, y)?; self.clear_line(w)?; } Ok(()) } /// just clears the char at the bottom right. /// (any redraw of this position makes the whole terminal flicker on some /// terminals like win/conemu, so we draw it only once at start of the /// app) pub fn clear_bottom_right_char( &self, w: &mut W, panel_skin: &PanelSkin, ) -> Result<(), ProgramError> { self.goto(w, self.width, self.height)?; panel_skin.styles.default.queue(w, ' ')?; Ok(()) } } broot-1.46.3/src/display/status_line.rs000064400000000000000000000030241046102023000161770ustar 00000000000000use { super::{Screen, W}, crate::{ app::Status, errors::ProgramError, skin::PanelSkin, }, termimad::{ minimad::{Alignment, Composite}, Area, StyledChar, }, }; /// write the whole status line (task + status) pub fn write( w: &mut W, task: Option<&str>, status: &Status, area: &Area, panel_skin: &PanelSkin, screen: Screen, ) -> Result<(), ProgramError> { let y = area.top; screen.goto(w, area.left, y)?; let mut x = area.left; if let Some(pending_task) = task { let pending_task = format!(" {pending_task}… "); x += pending_task.chars().count() as u16; panel_skin.styles.status_job.queue(w, pending_task)?; } screen.goto(w, x, y)?; let style = if status.error { &panel_skin.status_skin.error } else { &panel_skin.status_skin.normal }; style.write_inline_on(w, " ")?; let remaining_width = (area.width - (x - area.left) - 1) as usize; style.write_composite_fill( w, Composite::from_inline(&status.message), remaining_width, Alignment::Unspecified, )?; Ok(()) } /// erase the whole status line pub fn erase( w: &mut W, area: &Area, panel_skin: &PanelSkin, screen: Screen, ) -> Result<(), ProgramError> { screen.goto(w, area.left, area.top)?; let sc = StyledChar::new( panel_skin.status_skin.normal.paragraph.compound_style.clone(), ' ', ); sc.queue_repeat(w, area.width as usize)?; Ok(()) } broot-1.46.3/src/errors.rs000064400000000000000000000154601046102023000135230ustar 00000000000000//! Definitions of custom errors used in broot use { custom_error::custom_error, image::error::ImageError, lazy_regex::regex, std::io, }; custom_error! {pub ProgramError AmbiguousVerbName {name: String} = "Ambiguous name: More than one verb matches {name:?}", ArgParse {bad: String, valid: String} = "{bad:?} can't be parsed (valid values: {valid:?})", ConfFile {path:String, details: ConfError} = "Bad configuration file {path:?} : {details}", Conf {source: ConfError} = "Bad configuration: {source}", ImageError {source: ImageError } = "{source}", InternalError {details: String} = "Internal error: {details}", // should not happen Io {source: io::Error} = "IO Error : {source}", LaunchError {program: String, source: io::Error} = "Unable to launch {program}: {source}", Lfs {details: String} = "Failed to fetch mounts: {details}", NetError {source: NetError} = "{source}", OpenError { source: opener::OpenError } = "Open error: {source}", ShelInstall { source: ShellInstallError } = "{source}", Svg {source: SvgError} = "SVG error: {source}", SyntectCrashed { details: String } = "Syntect crashed on {details:?}", Termimad {source: termimad::Error} = "Termimad Error : {source}", Trash {message: String} = "Trash error: {message}", TreeBuild {source: TreeBuildError} = "{source}", UnknowShell {shell: String} = "Unknown shell: {shell}", UnknownVerb {name: String} = "No verb matches {name:?}", UnmappableFile = "File can't be mapped", UnmatchingVerbArgs {name: String} = "No matching argument found for verb {name:?}", UnprintableFile = "File can't be printed", // has characters that can't be printed without escaping Unrecognized {token: String} = "Unrecognized: {token}", ZeroLenFile = "File seems empty", } custom_error! {pub ShellInstallError Io {source: io::Error, when: String} = "IO Error {source} on {when}", } impl ShellInstallError { pub fn is_permission_denied(&self) -> bool { match self { Self::Io { source, .. } => { if source.kind() == io::ErrorKind::PermissionDenied { true } else { cfg!(windows) && source.raw_os_error().unwrap_or(0) == 1314 } } } } } pub trait IoToShellInstallError { fn context( self, f: &dyn Fn() -> String, ) -> Result; } impl IoToShellInstallError for Result { fn context( self, f: &dyn Fn() -> String, ) -> Result { self.map_err(|source| ShellInstallError::Io { source, when: f() }) } } custom_error! {pub TreeBuildError FileNotFound { path: String } = "File not found: {path}", Interrupted = "Task Interrupted", InvalidUtf8 { path: String } = "Invalid UTF-8 in {path}", //Io {source: io::Error} = "IO Error : {source}", NotADirectory { path: String } = "Not a directory: {path}", NotARootDescendant { path: String } = "Not a descendant of the root: {path}", TooManyMatches { max: usize } = "Too many matches (max allowed: {max})", InconsistentData { message:String } = "Inconsistent data: {message}", // maybe refresh ? } custom_error! {pub ConfError Io {source: io::Error} = "unable to read from the file: {source}", ImportNotFound {path: String} = "import file not found: {path:?}", UnknownFileExtension { path: String} = "unexpected file extension in {path:?}", Toml {source: toml::de::Error} = "unable to parse TOML: {source}", Hjson {source: deser_hjson::Error} = "unable to parse Hjson: {source}", Invalid = "unexpected conf structure", // not expected MissingField {txt: String} = "missing field in conf", InvalidVerbInvocation {invocation: String} = "invalid verb invocation: {invocation}", InvalidVerbConf {details: String} = "invalid verb conf: {details}", UnknownInternal {verb: String} = "not a known internal: {verb}", InvalidSearchMode {details: String} = "invalid search mode: {details}", InvalidKey {raw: String} = "not a valid key: {raw}", ParseKey {source: crokey::ParseKeyError} = "{source}", ReservedKey {key: String} = "reserved key: {key}", UnexpectedInternalArg {invocation: String} = "unexpected argument for internal: {invocation}", InvalidCols {details: String} = "invalid cols definition: {details}", InvalidSkin {source: InvalidSkinError} = "invalid skin: {source}", InvalidThreadsCount { count: usize } = "invalid threads count: {count}", InvalidDefaultFlags { flags: String } = "invalid default flags: {flags:?}", InvalidSyntaxTheme { name: String } = "invalid syntax theme: {name:?}", InvalidGlobPattern { pattern: String } = "invalid glob pattern: {pattern:?}", InvalidVerbName { name: String } = "invalid verb name: {name:?} (must either not start with a special character or be only made of special characters)", } // error which can be raised when parsing a pattern the user typed custom_error! {pub PatternError InvalidMode { mode: String } = "Invalid search mode: {mode:?}", InvalidRegex {source: regex::Error} = @{ format!("Invalid Regular Expression: {}", source.to_string().lines().last().unwrap_or("")) }, UnknownRegexFlag {bad: char} = "Unknown regular expression flag: {bad:?}", } custom_error! {pub InvalidSkinError InvalidColor {source: termimad::ParseColorError} = "invalid color: {source}", InvalidAttribute {raw : String} = "'{raw}' is not a valid style attribute", InvalidGreyLevel {level: u8} = "grey level must be between 0 and 23 (got {level})", InvalidStyle {style: String} = "Invalid skin style : {style}", InvalidStyleToken {source: termimad::ParseStyleTokenError} = "{source}", } custom_error! {pub NetError SocketNotAvailable { path : String } = "Can't open socket: {path} already exists - consider removing it", Io {source: io::Error} = "error on the socket: {source}", InvalidMessage = "invalid message received", } custom_error! {pub SvgError Io {source: io::Error} = "IO Error : {source}", Internal { message: &'static str } = "Internal error : {message}", Svg {source: resvg::usvg::Error} = "SVG Error: {source}", } custom_error! {pub PreviewTransformerError InvalidInput = "Invalid input", NoOutput = "No output", Io {source: io::Error} = "IO Error : {source}", ProcessInterrupted = "Process interrupted", ProcessFailed { code: i32 } = "Execution failed with code {code}", } broot-1.46.3/src/file_sum/mod.rs000064400000000000000000000075541046102023000145760ustar 00000000000000/// compute consolidated data for directories: modified date, size, and count. /// A cache is used to avoid recomputing the same directories again and again. /// On unix, hard links are checked to avoid counting twice an inode. mod sum_computation; use { crate::{ app::*, task_sync::Dam, }, once_cell::sync::Lazy, rustc_hash::FxHashMap, std::{ ops::AddAssign, path::{ Path, PathBuf, }, sync::Mutex, }, }; pub const DEFAULT_THREAD_COUNT: usize = 5; static SUM_CACHE: Lazy>> = Lazy::new(|| Mutex::new(FxHashMap::default())); pub fn clear_cache() { SUM_CACHE.lock().unwrap().clear(); } /// Reduction of counts, dates and sizes on a file or directory #[derive(Debug, Copy, Clone)] pub struct FileSum { real_size: u64, // bytes, the space it takes on disk count: usize, // number of files modified: u32, // seconds from Epoch to last modification, or 0 if there was an error sparse: bool, // only for non directories: tells whether the file is sparse } impl FileSum { pub fn new( real_size: u64, sparse: bool, count: usize, modified: u32, ) -> Self { Self { real_size, count, modified, sparse, } } pub fn zero() -> Self { Self::new(0, false, 0, 0) } pub fn incr(&mut self) { self.count += 1; } /// return the sum of the given file, which is assumed /// to be a normal file (ie not a directory) pub fn from_file(path: &Path) -> Self { sum_computation::compute_file_sum(path) } /// Return the sum of the directory, either by computing it of by /// fetching it from cache. /// If the lifetime expires before complete computation, None is returned. pub fn from_dir( path: &Path, dam: &Dam, con: &AppContext, ) -> Option { let mut sum_cache = SUM_CACHE.lock().unwrap(); match sum_cache.get(path) { Some(sum) => Some(*sum), None => { let sum = time!( "sum computation", path, sum_computation::compute_dir_sum(path, &mut sum_cache, dam, con), ); if let Some(sum) = sum { sum_cache.insert(PathBuf::from(path), sum); } sum } } } pub fn part_of_size( self, total: Self, ) -> f32 { if total.real_size == 0 { 0.0 } else { self.real_size as f32 / total.real_size as f32 } } /// return the number of files (normally at least 1) pub fn to_count(self) -> usize { self.count } /// return the number of seconds from Epoch to last modification, /// or 0 if the computation failed pub fn to_seconds(self) -> u32 { self.modified } /// return the size in bytes pub fn to_size(self) -> u64 { self.real_size } pub fn to_valid_seconds(self) -> Option { if self.modified != 0 { Some(self.modified as i64) } else { None } } /// tell whether the file has holes (in which case the size displayed by /// other tools may be greater than the "real" one returned by broot). /// Not computed (return false) on windows or for directories. pub fn is_sparse(self) -> bool { self.sparse } } impl AddAssign for FileSum { #[allow(clippy::suspicious_op_assign_impl)] fn add_assign( &mut self, other: Self, ) { *self = Self::new( self.real_size + other.real_size, self.sparse | other.sparse, self.count + other.count, self.modified.max(other.modified), ); } } broot-1.46.3/src/file_sum/sum_computation.rs000064400000000000000000000254711046102023000172430ustar 00000000000000use { super::FileSum, crate::{ app::*, path::*, task_sync::Dam, }, rayon::{ ThreadPool, ThreadPoolBuilder, }, rustc_hash::{ FxHashMap, }, std::{ convert::TryInto, fs, path::{ Path, PathBuf, }, sync::{ atomic::{ AtomicIsize, Ordering, }, Arc, Mutex, }, }, termimad::crossbeam::channel, }; #[cfg(unix)] use { std::os::unix::fs::MetadataExt, }; struct DirSummer { thread_count: usize, thread_pool: ThreadPool, } /// a node id, taking the device into account to be sure to discriminate /// nodes with the same inode but on different devices #[cfg(unix)] #[derive(Debug, Clone, Copy, PartialEq, Hash, Eq)] struct NodeId { /// inode number inode: u64, /// device number dev: u64, } impl DirSummer { pub fn new(thread_count: usize) -> Self { let thread_pool = ThreadPoolBuilder::new() .num_threads(thread_count) .build() .unwrap(); Self { thread_count, thread_pool, } } /// compute the consolidated numbers for a directory, with implementation /// varying depending on the OS: /// On unix, the computation is done on blocks of 512 bytes /// see https://doc.rust-lang.org/std/os/unix/fs/trait.MetadataExt.html#tymethod.blocks pub fn compute_dir_sum( &mut self, path: &Path, cache: &mut FxHashMap, dam: &Dam, con: &AppContext, ) -> Option { let threads_count = self.thread_count; if con.special_paths.sum(path) == Directive::Never { return Some(FileSum::zero()); } // there are problems in /proc - See issue #637 if path.starts_with("/proc") { debug!("not summing in /proc"); return Some(FileSum::zero()); } if path.starts_with("/run") && !path.starts_with("/run/media") { debug!("not summing in /run"); return Some(FileSum::zero()); } // to avoid counting twice a node, we store their id in a set #[cfg(unix)] let nodes = Arc::new(Mutex::new(rustc_hash::FxHashSet::::default())); // busy is the number of directories which are either being processed or queued // We use this count to determine when threads can stop waiting for tasks let mut busy = 0; let mut sum = compute_file_sum(path); // this MPMC channel contains the directory paths which must be handled. // A None means there's nothing left and the thread may send its result and stop let (dirs_sender, dirs_receiver) = channel::unbounded(); let special_paths = con.special_paths.reduce(path); // the first level is managed a little differently: we look at the cache // before adding. This enables faster computations in two cases: // - for the root line (assuming it's computed after the content) // - when we navigate up the tree if let Ok(entries) = fs::read_dir(path) { for e in entries.flatten() { if let Ok(md) = e.metadata() { if md.is_dir() { let entry_path = e.path(); if con.special_paths.sum(&entry_path) == Directive::Never { debug!("not summing special path {:?}", entry_path); continue; } // we check the cache if let Some(entry_sum) = cache.get(&entry_path) { sum += *entry_sum; continue; } // we add the directory to the channel of dirs needing // processing busy += 1; dirs_sender.send(Some(entry_path)).unwrap(); } else { #[cfg(unix)] if md.nlink() > 1 { let mut nodes = nodes.lock().unwrap(); let node_id = NodeId { inode: md.ino(), dev: md.dev(), }; if !nodes.insert(node_id) { // it was already in the set continue; } } } sum += md_sum(&md); } } } if busy == 0 { return Some(sum); } let busy = Arc::new(AtomicIsize::new(busy)); // this MPMC channel is here for the threads to send their results // at end of computation let (thread_sum_sender, thread_sum_receiver) = channel::bounded(threads_count); // Each thread does a summation without merge and the data are merged // at the end (this avoids waiting for a mutex during computation) for _ in 0..threads_count { let busy = Arc::clone(&busy); let (dirs_sender, dirs_receiver) = (dirs_sender.clone(), dirs_receiver.clone()); #[cfg(unix)] let nodes = nodes.clone(); let special_paths = special_paths.clone(); let observer = dam.observer(); let thread_sum_sender = thread_sum_sender.clone(); self.thread_pool.spawn(move || { let mut thread_sum = FileSum::zero(); loop { let o = dirs_receiver.recv(); if let Ok(Some(open_dir)) = o { if let Ok(entries) = fs::read_dir(open_dir) { for e in entries.flatten() { if let Ok(md) = e.metadata() { if md.is_dir() { let path = e.path(); if special_paths.sum(&path) == Directive::Never { debug!("not summing (deep) special path {:?}", path); continue; } // we add the directory to the channel of dirs needing // processing busy.fetch_add(1, Ordering::Relaxed); dirs_sender.send(Some(path)).unwrap(); } else { #[cfg(unix)] if md.nlink() > 1 { let mut nodes = nodes.lock().unwrap(); let node_id = NodeId { inode: md.ino(), dev: md.dev(), }; if !nodes.insert(node_id) { // it was already in the set continue; } } } thread_sum += md_sum(&md); } else { // we can't measure much but we can count the file thread_sum.incr(); } } } busy.fetch_sub(1, Ordering::Relaxed); } if observer.has_event() { dirs_sender.send(None).unwrap(); // to unlock the next waiting thread break; } if busy.load(Ordering::Relaxed) < 1 { dirs_sender.send(None).unwrap(); // to unlock the next waiting thread break; } } thread_sum_sender.send(thread_sum).unwrap(); }); } // Wait for the threads to finish and consolidate their results for _ in 0..threads_count { match thread_sum_receiver.recv() { Ok(thread_sum) => { sum += thread_sum; } Err(e) => { warn!("Error while recv summing thread result : {:?}", e); } } } if dam.has_event() { return None; } Some(sum) } } /// compute the consolidated numbers for a directory, with implementation /// varying depending on the OS: /// On unix, the computation is done on blocks of 512 bytes /// see https://doc.rust-lang.org/std/os/unix/fs/trait.MetadataExt.html#tymethod.blocks pub fn compute_dir_sum( path: &Path, cache: &mut FxHashMap, dam: &Dam, con: &AppContext, ) -> Option { use once_cell::sync::OnceCell; static DIR_SUMMER: OnceCell> = OnceCell::new(); DIR_SUMMER .get_or_init(|| Mutex::new(DirSummer::new(con.file_sum_threads_count))) .lock() .unwrap() .compute_dir_sum(path, cache, dam, con) } /// compute the sum for a regular file (not a folder) pub fn compute_file_sum(path: &Path) -> FileSum { match fs::symlink_metadata(path) { Ok(md) => { let seconds = extract_seconds(&md); #[cfg(unix)] { let nominal_size = md.size(); let block_size = md.blocks() * 512; FileSum::new( block_size.min(nominal_size), block_size < nominal_size, 1, seconds, ) } #[cfg(not(unix))] FileSum::new(md.len(), false, 1, seconds) } Err(_) => FileSum::new(0, false, 1, 0), } } #[cfg(unix)] #[inline(always)] fn extract_seconds(md: &fs::Metadata) -> u32 { md.mtime().try_into().unwrap_or(0) } #[cfg(not(unix))] #[inline(always)] fn extract_seconds(md: &fs::Metadata) -> u32 { if let Ok(st) = md.modified() { if let Ok(d) = st.duration_since(std::time::UNIX_EPOCH) { if let Ok(secs) = d.as_secs().try_into() { return secs; } } } 0 } #[inline(always)] fn md_sum(md: &fs::Metadata) -> FileSum { #[cfg(unix)] let size = md.blocks() * 512; #[cfg(not(unix))] let size = md.len(); let seconds = extract_seconds(md); FileSum::new(size, false, 1, seconds) } broot-1.46.3/src/filesystems/filesystems_state.rs000064400000000000000000000542511046102023000203260ustar 00000000000000use { super::*, crate::{ app::*, browser::BrowserState, command::*, display::*, errors::ProgramError, pattern::*, task_sync::Dam, tree::TreeOptions, verb::*, }, crokey::crossterm::{ cursor, style::Color, QueueableCommand, }, lfs_core::Mount, std::{ convert::TryInto, fs, os::unix::fs::MetadataExt, path::Path, }, strict::NonEmptyVec, termimad::{ minimad::Alignment, *, }, }; struct FilteredContent { pattern: Pattern, mounts: Vec, // may be empty selection_idx: usize, } /// an application state showing the currently mounted filesystems pub struct FilesystemState { mounts: NonEmptyVec, selection_idx: usize, scroll: usize, page_height: usize, tree_options: TreeOptions, filtered: Option, mode: Mode, } impl FilesystemState { /// create a state listing the filesystem, trying to select /// the one containing the path given in argument. /// Not finding any filesystem is considered an error and prevents /// the opening of this state. pub fn new( path: Option<&Path>, tree_options: TreeOptions, con: &AppContext, ) -> Result { let mut mount_list = MOUNTS.lock().unwrap(); let show_only_disks = false; let mounts = mount_list .load()? .iter() .filter(|mount| { if show_only_disks { mount.disk.is_some() } else { mount.stats().is_some() } }) .cloned() .collect::>(); let mounts: NonEmptyVec = match mounts.try_into() { Ok(nev) => nev, _ => { return Err(ProgramError::Lfs { details: "no disk in lfs-core list".to_string(), }); } }; let selection_idx = path .and_then(|path| fs::metadata(path).ok()) .and_then(|md| { let device_id = md.dev().into(); mounts.iter().position(|m| m.info.dev == device_id) }) .unwrap_or(0); Ok(FilesystemState { mounts, selection_idx, scroll: 0, page_height: 0, tree_options, filtered: None, mode: con.initial_mode(), }) } pub fn count(&self) -> usize { self.filtered .as_ref() .map(|f| f.mounts.len()) .unwrap_or_else(|| self.mounts.len().into()) } pub fn try_scroll( &mut self, cmd: ScrollCommand, ) -> bool { let old_scroll = self.scroll; self.scroll = cmd.apply(self.scroll, self.count(), self.page_height); if self.selection_idx < self.scroll { self.selection_idx = self.scroll; } else if self.selection_idx >= self.scroll + self.page_height { self.selection_idx = self.scroll + self.page_height - 1; } self.scroll != old_scroll } /// change the selection fn move_line( &mut self, internal_exec: &InternalExecution, input_invocation: Option<&VerbInvocation>, dir: i32, // -1 for up, 1 for down cycle: bool, ) -> CmdResult { let count = get_arg(input_invocation, internal_exec, 1); let dir = dir * count; if let Some(f) = self.filtered.as_mut() { f.selection_idx = move_sel(f.selection_idx, f.mounts.len(), dir, cycle); } else { self.selection_idx = move_sel(self.selection_idx, self.mounts.len().get(), dir, cycle); } if self.selection_idx < self.scroll { self.scroll = self.selection_idx; } else if self.selection_idx >= self.scroll + self.page_height { self.scroll = self.selection_idx + 1 - self.page_height; } CmdResult::Keep } fn no_opt_selected_path(&self) -> &Path { &self.mounts[self.selection_idx].info.mount_point } fn no_opt_selection(&self) -> Selection<'_> { Selection { path: self.no_opt_selected_path(), stype: SelectionType::Directory, is_exe: false, line: 0, } } } impl PanelState for FilesystemState { fn get_type(&self) -> PanelStateType { PanelStateType::Fs } fn set_mode(&mut self, mode: Mode) { self.mode = mode; } fn get_mode(&self) -> Mode { self.mode } fn selected_path(&self) -> Option<&Path> { Some(self.no_opt_selected_path()) } fn tree_options(&self) -> TreeOptions { self.tree_options.clone() } fn with_new_options( &mut self, _screen: Screen, change_options: &dyn Fn(&mut TreeOptions) -> &'static str, _in_new_panel: bool, // TODO open tree if true _con: &AppContext, ) -> CmdResult { change_options(&mut self.tree_options); CmdResult::Keep } fn selection(&self) -> Option> { Some(self.no_opt_selection()) } fn refresh(&mut self, _screen: Screen, _con: &AppContext) -> Command { Command::empty() } fn on_pattern( &mut self, pattern: InputPattern, _app_state: &AppState, _con: &AppContext, ) -> Result { if pattern.is_none() { self.filtered = None; } else { let mut selection_idx = 0; let mut mounts = Vec::new(); let pattern = pattern.pattern; for (idx, mount) in self.mounts.iter().enumerate() { if pattern.score_of_string(&mount.info.fs).is_none() && mount.disk.as_ref().and_then(|d| pattern.score_of_string(d.disk_type())).is_none() && pattern.score_of_string(&mount.info.fs_type).is_none() && pattern.score_of_string(&mount.info.mount_point.to_string_lossy()).is_none() { continue; } if idx <= self.selection_idx { selection_idx = mounts.len(); } mounts.push(mount.clone()); } self.filtered = Some(FilteredContent { pattern, mounts, selection_idx, }); } Ok(CmdResult::Keep) } fn display( &mut self, w: &mut W, disc: &DisplayContext, ) -> Result<(), ProgramError> { let area = &disc.state_area; let con = &disc.con; self.page_height = area.height as usize - 2; let (mounts, selection_idx) = if let Some(filtered) = &self.filtered { (filtered.mounts.as_slice(), filtered.selection_idx) } else { (self.mounts.as_slice(), self.selection_idx) }; let scrollbar = area.scrollbar(self.scroll, mounts.len()); //- style preparation let styles = &disc.panel_skin.styles; let selection_bg = styles.selected_line.get_bg() .unwrap_or(Color::AnsiValue(240)); let match_style = &styles.char_match; let mut selected_match_style = styles.char_match.clone(); selected_match_style.set_bg(selection_bg); let border_style = &styles.help_table_border; let mut selected_border_style = styles.help_table_border.clone(); selected_border_style.set_bg(selection_bg); //- width computations and selection of columns to display let width = area.width as usize; let w_fs = mounts.iter() .map(|m| m.info.fs.chars().count()) .max().unwrap_or(0) .max("filesystem".len()); let mut wc_fs = w_fs; // width of the column (may include selection mark) if con.show_selection_mark { wc_fs += 1; } let w_dsk = 5; // max width of a lfs-core disk type let w_type = mounts.iter() .map(|m| m.info.fs_type.chars().count()) .max().unwrap_or(0) .max("type".len()); let w_size = 4; let w_use = 4; let mut w_use_bar = 1; // min size, may grow if space available let w_use_share = 4; let mut wc_use = w_use; // sum of all the parts of the usage column let w_free = 4; let w_mount_point = mounts.iter() .map(|m| m.info.mount_point.to_string_lossy().chars().count()) .max().unwrap_or(0) .max("mount point".len()); let w_mandatory = wc_fs + 1 + w_size + 1 + w_free + 1 + w_mount_point; let mut e_dsk = false; let mut e_type = false; let mut e_use_bar = false; let mut e_use_share = false; let mut e_use = false; if w_mandatory + 1 < width { let mut rem = width - w_mandatory - 1; if rem > w_use { rem -= w_use + 1; e_use = true; } if e_use && rem > w_use_share { rem -= w_use_share; // no separation with use e_use_share = true; wc_use += w_use_share; } if rem > w_dsk { rem -= w_dsk + 1; e_dsk = true; } if e_use && rem > w_use_bar { rem -= w_use_bar + 1; e_use_bar = true; wc_use += w_use_bar + 1; } if rem > w_type { rem -= w_type + 1; e_type = true; } if e_use_bar && rem > 0 { let incr = rem.min(9); w_use_bar += incr; wc_use += incr; } } //- titles w.queue(cursor::MoveTo(area.left, area.top))?; let mut cw = CropWriter::new(w, width); cw.queue_g_string(&styles.default, format!("{:wc_fs$}", "filesystem"))?; cw.queue_char(border_style, '│')?; if e_dsk { cw.queue_g_string(&styles.default, "disk ".to_string())?; cw.queue_char(border_style, '│')?; } if e_type { cw.queue_g_string(&styles.default, format!("{:^w_type$}", "type"))?; cw.queue_char(border_style, '│')?; } if e_use { cw.queue_g_string(&styles.default, format!( "{:^width$}", if wc_use > 4 { "usage" } else { "use" }, width = wc_use ))?; cw.queue_char(border_style, '│')?; } cw.queue_g_string(&styles.default, "free".to_string())?; cw.queue_char(border_style, '│')?; cw.queue_g_string(&styles.default, "size".to_string())?; cw.queue_char(border_style, '│')?; cw.queue_g_string(&styles.default, "mount point".to_string())?; cw.fill(border_style, &SPACE_FILLING)?; //- horizontal line w.queue(cursor::MoveTo(area.left, 1 + area.top))?; let mut cw = CropWriter::new(w, width); cw.queue_g_string(border_style, format!("{:─>width$}", '┼', width = wc_fs + 1))?; if e_dsk { cw.queue_g_string(border_style, format!("{:─>width$}", '┼', width = w_dsk + 1))?; } if e_type { cw.queue_g_string(border_style, format!("{:─>width$}", '┼', width = w_type+1))?; } cw.queue_g_string(border_style, format!("{:─>width$}", '┼', width = w_size+1))?; if e_use { cw.queue_g_string(border_style, format!("{:─>width$}", '┼', width = wc_use+1))?; } cw.queue_g_string(border_style, format!("{:─>width$}", '┼', width = w_free+1))?; cw.fill(border_style, &BRANCH_FILLING)?; //- content let mut idx = self.scroll; for y in 2..area.height { w.queue(cursor::MoveTo(area.left, y + area.top))?; let selected = selection_idx == idx; let mut cw = CropWriter::new(w, width - 1); // -1 for scrollbar let txt_style = if selected { &styles.selected_line } else { &styles.default }; if let Some(mount) = mounts.get(idx) { let match_style = if selected { &selected_match_style } else { match_style }; let border_style = if selected { &selected_border_style } else { border_style }; if con.show_selection_mark { cw.queue_char(txt_style, if selected { '▶' } else { ' ' })?; } // fs let s = &mount.info.fs; let mut matched_string = MatchedString::new( self.filtered.as_ref().and_then(|f| f.pattern.search_string(s)), s, txt_style, match_style, ); matched_string.fill(w_fs, Alignment::Left); matched_string.queue_on(&mut cw)?; cw.queue_char(border_style, '│')?; // dsk if e_dsk { if let Some(disk) = mount.disk.as_ref() { let s = disk.disk_type(); let mut matched_string = MatchedString::new( self.filtered.as_ref().and_then(|f| f.pattern.search_string(s)), s, txt_style, match_style, ); matched_string.fill(5, Alignment::Center); matched_string.queue_on(&mut cw)?; } else { cw.queue_g_string(txt_style, " ".to_string())?; } cw.queue_char(border_style, '│')?; } // type if e_type { let s = &mount.info.fs_type; let mut matched_string = MatchedString::new( self.filtered.as_ref().and_then(|f| f.pattern.search_string(s)), s, txt_style, match_style, ); matched_string.fill(w_type, Alignment::Center); matched_string.queue_on(&mut cw)?; cw.queue_char(border_style, '│')?; } // size, used, free if let Some(stats) = mount.stats().filter(|s| s.size() > 0) { let share_color = styles.good_to_bad_color(stats.use_share()); // used if e_use { cw.queue_g_string(txt_style, format!("{:>4}", file_size::fit_4(stats.used())))?; if e_use_share { cw.queue_g_string(txt_style, format!("{:>3.0}%", 100.0*stats.use_share()))?; } if e_use_bar { cw.queue_char(txt_style, ' ')?; let pb = ProgressBar::new(stats.use_share() as f32, w_use_bar); let mut bar_style = styles.default.clone(); bar_style.set_bg(share_color); cw.queue_g_string(&bar_style, format!("{pb:4}", file_size::fit_4(stats.available())))?; cw.queue_char(border_style, '│')?; // size if let Some(stats) = mount.stats() { cw.queue_g_string(txt_style, format!("{:>4}", file_size::fit_4(stats.size())))?; } else { cw.repeat(txt_style, &SPACE_FILLING, 4)?; } cw.queue_char(border_style, '│')?; } else { // used if e_use { cw.repeat(txt_style, &SPACE_FILLING, wc_use)?; cw.queue_char(border_style, '│')?; } // free cw.repeat(txt_style, &SPACE_FILLING, w_free)?; cw.queue_char(border_style, '│')?; // size cw.repeat(txt_style, &SPACE_FILLING, w_size)?; cw.queue_char(border_style, '│')?; } // mount point let s = &mount.info.mount_point.to_string_lossy(); let matched_string = MatchedString::new( self.filtered.as_ref().and_then(|f| f.pattern.search_string(s)), s, txt_style, match_style, ); matched_string.queue_on(&mut cw)?; idx += 1; } cw.fill(txt_style, &SPACE_FILLING)?; let scrollbar_style = if ScrollCommand::is_thumb(y, scrollbar) { &styles.scrollbar_thumb } else { &styles.scrollbar_track }; scrollbar_style.queue_str(w, "▐")?; } Ok(()) } fn on_internal( &mut self, w: &mut W, invocation_parser: Option<&InvocationParser>, internal_exec: &InternalExecution, input_invocation: Option<&VerbInvocation>, trigger_type: TriggerType, app_state: &mut AppState, cc: &CmdContext, ) -> Result { let screen = cc.app.screen; let con = &cc.app.con; use Internal::*; Ok(match internal_exec.internal { Internal::back => { if let Some(f) = self.filtered.take() { if !f.mounts.is_empty() { self.selection_idx = self.mounts.iter() .position(|m| m.info.id == f.mounts[f.selection_idx].info.id) .unwrap(); // all filtered mounts come from self.mounts } CmdResult::Keep } else { CmdResult::PopState } } Internal::line_down => { self.move_line(internal_exec, input_invocation, 1, true) } Internal::line_up => { self.move_line(internal_exec, input_invocation, -1, true) } Internal::line_down_no_cycle => { self.move_line(internal_exec, input_invocation, 1, false) } Internal::line_up_no_cycle => { self.move_line(internal_exec, input_invocation, -1, false) } Internal::open_stay => { let in_new_panel = input_invocation .map(|inv| inv.bang) .unwrap_or(internal_exec.bang); let dam = Dam::unlimited(); let mut tree_options = self.tree_options(); tree_options.show_root_fs = true; CmdResult::from_optional_browser_state( BrowserState::new( self.no_opt_selected_path().to_path_buf(), tree_options, screen, con, &dam, ), None, in_new_panel, ) } Internal::panel_left => { let areas = &cc.panel.areas; if areas.is_first() && areas.nb_pos < con.max_panels_count { // we ask for the creation of a panel to the left internal_focus::new_panel_on_path( self.no_opt_selected_path().to_path_buf(), screen, self.tree_options(), PanelPurpose::None, con, HDir::Left, ) } else { // we ask the app to focus the panel to the left CmdResult::HandleInApp(Internal::panel_left_no_open) } } Internal::panel_left_no_open => CmdResult::HandleInApp(Internal::panel_left_no_open), Internal::panel_right => { let areas = &cc.panel.areas; if areas.is_last() && areas.nb_pos < con.max_panels_count { // we ask for the creation of a panel to the right internal_focus::new_panel_on_path( self.no_opt_selected_path().to_path_buf(), screen, self.tree_options(), PanelPurpose::None, con, HDir::Right, ) } else { // we ask the app to focus the panel to the right CmdResult::HandleInApp(Internal::panel_right_no_open) } } Internal::panel_right_no_open => CmdResult::HandleInApp(Internal::panel_right_no_open), Internal::page_down => { if !self.try_scroll(ScrollCommand::Pages(1)) { self.selection_idx = self.count() - 1; } CmdResult::Keep } Internal::page_up => { if !self.try_scroll(ScrollCommand::Pages(-1)) { self.selection_idx = 0; } CmdResult::Keep } open_leave => CmdResult::PopStateAndReapply, _ => self.on_internal_generic( w, invocation_parser, internal_exec, input_invocation, trigger_type, app_state, cc, )?, }) } fn on_click( &mut self, _x: u16, y: u16, _screen: Screen, _con: &AppContext, ) -> Result { if y >= 2 { let y = y as usize - 2 + self.scroll; let len: usize = self.mounts.len().into(); if y < len { self.selection_idx = y; } } Ok(CmdResult::Keep) } } broot-1.46.3/src/filesystems/mod.rs000064400000000000000000000007631046102023000153350ustar 00000000000000//! The whole module is only available on unix now mod filesystems_state; mod mount_list; mod mount_space_display; pub use { filesystems_state::FilesystemState, mount_list::MountList, mount_space_display::MountSpaceDisplay, }; use { once_cell::sync::Lazy, std::sync::Mutex, }; pub static MOUNTS: Lazy> = Lazy::new(|| Mutex::new(MountList::default())); pub fn clear_cache() { let mut mount_list = MOUNTS.lock().unwrap(); mount_list.clear_cache(); } broot-1.46.3/src/filesystems/mount_list.rs000064400000000000000000000027771046102023000167620ustar 00000000000000 use { crate::{ errors::ProgramError, }, lfs_core::{ DeviceId, Mount, read_mounts, ReadOptions, }, }; #[derive(Default)] pub struct MountList { mounts: Option>, } impl MountList { pub fn clear_cache(&mut self) { self.mounts = None; } /// try to load the mounts if they aren't loaded. pub fn load(&mut self) -> Result<&Vec, ProgramError> { if self.mounts.is_none() { let mut options = ReadOptions::default(); options.remote_stats(false); match read_mounts(&options) { Ok(mut vec) => { debug!("{} mounts loaded", vec.len()); vec.sort_by_key(|m| { let size = m.stats().map_or(0, |s| s.size()); u64::MAX - size }); self.mounts = Some(vec); } Err(e) => { warn!("Failed to load mounts: {:?}", e); return Err(ProgramError::Lfs { details: e.to_string(), }); } } } Ok( // this unwrap will be fixed as soon as there's option.insert in stable self.mounts.as_ref().unwrap() ) } pub fn get_by_device_id(&self, dev: DeviceId) -> Option<&Mount> { self.mounts.as_ref() .and_then(|mounts| mounts.iter().find(|m| m.info.dev == dev)) } } broot-1.46.3/src/filesystems/mount_space_display.rs000064400000000000000000000104271046102023000206160ustar 00000000000000use { crate::{ display::cond_bg, errors::ProgramError, skin::StyleMap, }, crokey::crossterm::{ style::{ResetColor, SetBackgroundColor, SetForegroundColor}, QueueableCommand, }, lfs_core::Mount, termimad::*, }; /// an abstract of the space info relative to a block device. /// It's supposed to be shown on top of screen next to the root. pub struct MountSpaceDisplay<'m, 's> { mount: &'m Mount, skin: &'s StyleMap, pub available_width: usize, } impl<'m, 's> MountSpaceDisplay<'m, 's> { pub fn from(mount: &'m Mount, skin: &'s StyleMap, available_width: usize) -> Self { Self { mount, skin, available_width, } } pub fn write( &self, cw: &mut CropWriter, selected: bool, ) -> Result<(), ProgramError> where W: std::io::Write, { if self.available_width < 4 { return Ok(()); } let bg = if selected { self.skin.selected_line.get_bg() } else { self.skin.default.get_bg() }; cond_bg!(txt_style, self, selected, self.skin.default); let w_fs = self.mount.info.fs.chars().count(); if let Some(s) = &self.mount.stats() { //- width computation let mut e_fs = false; let dsk = self.mount.disk.as_ref().map_or("", |d| d.disk_type()); let w_dsk = dsk.chars().count(); let mut e_dsk = false; let w_fraction = 9; let mut e_fraction = false; let mut w_bar = 2; // min width let mut e_bar = false; let w_percent = 4; let mut rem = self.available_width - w_percent; let share_color = self.skin.good_to_bad_color(s.use_share()); if rem > 1 { // left margin for readability rem -= 1; cw.queue_char(txt_style, ' ')?; } if rem > w_fs { rem -= w_fs + 1; // 1 for margin e_fs = true; } if rem > w_fraction { rem -= w_fraction + 1; e_fraction = true; } if rem > w_bar { rem -= w_bar + 1; e_bar = true; } if rem > w_dsk && w_dsk > 0 { rem -= w_dsk + 1; e_dsk = true; } if e_bar && rem > 0 { w_bar += rem.min(7); } //- display if e_fs { cw.queue_g_string(txt_style, format!(" {}", &self.mount.info.fs))?; } if e_dsk { cw.queue_char(txt_style, ' ')?; cw.queue_g_string(txt_style, dsk.to_string())?; } if e_fraction { if let Some(bg_color) = bg { cw.w.queue(SetBackgroundColor(bg_color))?; } else { cw.w.queue(ResetColor {})?; } cw.w.queue(SetForegroundColor(share_color))?; cw.queue_unstyled_char(' ')?; cw.queue_unstyled_g_string(file_size::fit_4(s.used()))?; cw.queue_g_string(txt_style, format!("/{}", file_size::fit_4(s.size())))?; } if e_bar { let pb = ProgressBar::new(s.use_share() as f32, w_bar); cw.w.queue(ResetColor {})?; if let Some(bg_color) = bg { cw.w.queue(SetBackgroundColor(bg_color))?; } cw.queue_unstyled_char(' ')?; cw.w.queue(SetBackgroundColor(share_color))?; cw.queue_unstyled_g_string(format!("{pb:3.0}%", 100.0 * s.use_share()))?; } else { // there's not much to print if there's no size info cw.queue_g_string(txt_style, format!(" {}", &self.mount.info.fs))?; } cw.w.queue(ResetColor {})?; Ok(()) } } broot-1.46.3/src/flag/mod.rs000064400000000000000000000002341046102023000136700ustar 00000000000000 /// Right now the flag is just a vessel for display. #[derive(Clone, Copy)] pub struct Flag { pub name: &'static str, pub value: &'static str, } broot-1.46.3/src/git/ignore.rs000064400000000000000000000222471046102023000142560ustar 00000000000000//! Implements parsing and applying .gitignore and .ignore files. // TODO rename without the "Git" prefix, as it's not only for gitignore use { git2, glob, id_arena::{Arena, Id}, lazy_regex::regex, once_cell::sync::Lazy, std::{ fmt, fs::File, io::{BufRead, BufReader, Result}, path::{Path, PathBuf}, }, }; #[derive(Default)] pub struct Ignorer { files: Arena, } #[derive(Debug, Clone, Default)] pub struct IgnoreChain { in_repo: bool, file_ids: Vec>, } /// The rules of a gitignore file #[derive(Debug, Clone)] pub struct IgnoreFile { rules: Vec, /// whether this is a git dedicated file (as opposed to a .ignore file) git: bool, local_git_ignore: bool, } /// a simple rule of a gitignore file #[derive(Clone)] struct IgnoreRule { ok: bool, // does this rule when matched means the file is good? (usually false) directory: bool, // whether this rule only applies to directories filename: bool, // does this rule apply to just the filename pattern: glob::Pattern, pattern_options: glob::MatchOptions, } impl fmt::Debug for IgnoreRule { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("IgnoreRule") .field("ok", &self.ok) .field("directory", &self.directory) .field("filename", &self.filename) .field("pattern", &self.pattern.as_str()) .finish() } } impl IgnoreRule { /// parse a line of a .gitignore file. /// The ref_dir is used if the line starts with '/' fn from(line: &str, ref_dir: &Path) -> Option { if line.starts_with('#') { return None; // comment line } let r = regex!( r"(?x) ^\s* (!)? # 1 : negation (.+?) # 2 : pattern (/)? # 3 : directory \s*$ " ); if let Some(c) = r.captures(line) { if let Some(p) = c.get(2) { let p = p.as_str(); let has_separator = p.contains('/'); let p = if has_separator { if p.starts_with('/') { format!("{}{}", ref_dir.to_string_lossy(), p) } else { format!("**/{}", p) } } else { p.to_string() }; match glob::Pattern::new(&p) { Ok(pattern) => { let pattern_options = glob::MatchOptions { case_sensitive: true, require_literal_leading_dot: false, require_literal_separator: has_separator, }; return Some(IgnoreRule { ok: c.get(1).is_some(), // if negation pattern, directory: c.get(3).is_some(), filename: !has_separator, pattern_options, }); } Err(e) => { info!(" wrong glob pattern {:?} : {}", &p, e); } } } } None } } impl IgnoreFile { /// build a new gitignore file, from either a global ignore file or /// a .gitignore file found inside a git repository. /// The ref_dir is either: /// - the path of the current repository for the global gitignore /// - the directory containing the .gitignore file pub fn new( file_path: &Path, ref_dir: &Path, local_git_ignore: bool, ) -> Result { let f = File::open(file_path)?; let git = file_path.file_name().map_or(false, |f| f == ".gitignore"); let mut rules: Vec = Vec::new(); for line in BufReader::new(f).lines() { if let Some(rule) = IgnoreRule::from(&line?, ref_dir) { rules.push(rule); } } // the last rule applicable to a path is the right one. So // we reverse the list to easily iterate from the last one to the first one rules.reverse(); Ok(IgnoreFile { git, rules, local_git_ignore, }) } /// return the global gitignore file interpreted for /// the given repo dir pub fn global(repo_dir: &Path) -> Option { static GLOBAL_GI_PATH: Lazy> = Lazy::new(find_global_ignore); if let Some(path) = &*GLOBAL_GI_PATH { IgnoreFile::new(path, repo_dir, true).ok() } else { None } } } pub fn find_global_ignore() -> Option { git2::Config::open_default() .and_then(|global_config| global_config.get_path("core.excludesfile")) .ok() .or_else(|| { directories::BaseDirs::new().map(|base_dirs| base_dirs.config_dir().join("git/ignore")) }) .or_else(|| { directories::UserDirs::new() .map(|user_dirs| user_dirs.home_dir().join(".config/git/ignore")) }) } impl IgnoreChain { pub fn push(&mut self, id: Id) { self.file_ids.push(id); } } impl Ignorer { pub fn root_chain(&mut self, mut dir: &Path) -> IgnoreChain { let mut chain = IgnoreChain::default(); loop { let is_repo = is_repo(dir); if is_repo { if let Some(gif) = IgnoreFile::global(dir) { chain.push(self.files.alloc(gif)); } } for (filename, local_git_ignore) in [ (".gitignore", true), (".git/info/exclude", true), (".ignore", false), ] { if chain.in_repo && local_git_ignore { // we don't add outside .gitignore files when we're in a repo continue; } let file = dir.join(filename); if let Ok(gif) = IgnoreFile::new(&file, dir, local_git_ignore) { chain.push(self.files.alloc(gif)); } } if is_repo { chain.in_repo = true; } if let Some(parent) = dir.parent() { dir = parent; } else { break; } } chain } /// Build a new chain by going deeper in the file system. /// /// The chain contains /// - the global gitignore file (if any) /// - all the .ignore files found in the current directory and in parents /// - the .git/info/exclude file of the current git repository /// - all the .gitignore files found in the current directory and in parents but not outside /// the current git repository /// /// Deeper file have a bigger priority. /// .ignore files have a bigger priority than .gitignore files. pub fn deeper_chain(&mut self, parent_chain: &IgnoreChain, dir: &Path) -> IgnoreChain { let mut chain = if is_repo(dir) { let mut chain = IgnoreChain::default(); for &id in &parent_chain.file_ids { if !self.files[id].local_git_ignore { chain.file_ids.push(id); } } chain.in_repo = true; chain } else { parent_chain.clone() }; for (filename, local_git_ignore) in [ (".gitignore", true), (".ignore", false), ] { if local_git_ignore && !chain.in_repo { // we don't add outside .gitignore files when we're in a repo continue; } let ignore_file = dir.join(filename); if let Ok(gif) = IgnoreFile::new(&ignore_file, dir, local_git_ignore) { debug!("pushing GIF {:#?}", &gif); chain.push(self.files.alloc(gif)); } } chain } /// return true if the given path should not be ignored pub fn accepts( &self, chain: &IgnoreChain, path: &Path, filename: &str, directory: bool, ) -> bool { // we start with deeper files: deeper rules have a bigger priority for id in chain.file_ids.iter().rev() { let file = &self.files[*id]; if file.git && !chain.in_repo { // git rules are irrelevant outside a git repository continue; } for rule in &file.rules { if rule.directory && !directory { continue; } let ok = if rule.filename { rule.pattern.matches_with(filename, rule.pattern_options) } else { rule.pattern.matches_path_with(path, rule.pattern_options) }; if ok { // as we read the rules in reverse, the first applying is OK return rule.ok; } } } true } } pub fn is_repo(root: &Path) -> bool { root.join(".git").exists() } broot-1.46.3/src/git/mod.rs000064400000000000000000000012311046102023000135400ustar 00000000000000mod ignore; mod status; mod status_computer; pub use { ignore::{IgnoreChain, Ignorer}, status::{LineGitStatus, LineStatusComputer, TreeGitStatus}, status_computer::{clear_status_computer_cache, get_tree_status}, }; use std::path::{Path, PathBuf}; /// return the closest parent (or self) containing a .git file pub fn closest_repo_dir(mut path: &Path) -> Option { loop { let c = path.join(".git"); if c.exists() { return Some(path.to_path_buf()); } path = match path.parent() { Some(path) => path, None => { return None; } }; } } broot-1.46.3/src/git/status.rs000064400000000000000000000056641046102023000143220ustar 00000000000000use { git2::{ self, Repository, Status, }, rustc_hash::FxHashMap, std::path::{ Path, PathBuf, }, }; const INTERESTING: Status = Status::from_bits_truncate( Status::WT_NEW.bits() | Status::CONFLICTED.bits() | Status::WT_MODIFIED.bits() | Status::IGNORED.bits() ); /// A git status #[derive(Debug, Clone, Copy)] pub struct LineGitStatus { pub status: Status, } impl LineGitStatus { pub fn from( repo: &Repository, relative_path: &Path, ) -> Option { repo.status_file(relative_path) .ok() .map(|status| LineGitStatus { status }) } pub fn is_interesting(self) -> bool { self.status.intersects(INTERESTING) } } /// As a git repo can't tell whether a path has a status, this computer /// looks at all the statuses of the repo and build a map path->status /// which can then be efficiently queried pub struct LineStatusComputer { interesting_statuses: FxHashMap, } impl LineStatusComputer { pub fn from(repo: Repository) -> Option { let workdir = repo.workdir()?; let mut interesting_statuses = FxHashMap::default(); let statuses = repo.statuses(None).ok()?; for entry in statuses.iter() { let status = entry.status(); if status.intersects(INTERESTING) { if let Some(path) = entry.path() { let path = workdir.join(path); interesting_statuses.insert(path, status); } } } Some(Self { interesting_statuses, }) } pub fn line_status( &self, path: &Path, ) -> Option { self.interesting_statuses .get(path) .map(|&status| LineGitStatus { status }) } pub fn is_interesting( &self, path: &Path, ) -> bool { self.interesting_statuses.contains_key(path) } } #[derive(Debug, Clone)] pub struct TreeGitStatus { pub current_branch_name: Option, pub insertions: usize, pub deletions: usize, } impl TreeGitStatus { pub fn from(repo: &Repository) -> Option { let current_branch_name = repo .head() .ok() .and_then(|head| head.shorthand().map(String::from)); let stats = match repo.diff_index_to_workdir(None, None) { Ok(diff) => match diff.stats() { Ok(stats) => stats, Err(e) => { debug!("get stats failed : {:?}", e); return None; } }, Err(e) => { debug!("get diff failed : {:?}", e); return None; } }; Some(Self { current_branch_name, insertions: stats.insertions(), deletions: stats.deletions(), }) } } broot-1.46.3/src/git/status_computer.rs000064400000000000000000000077721046102023000162420ustar 00000000000000use { super::TreeGitStatus, crate::{ git, task_sync::{ Computation, ComputationResult, Dam, }, }, git2::Repository, once_cell::sync::Lazy, rustc_hash::FxHashMap, std::{ path::{ Path, PathBuf, }, sync::Mutex, }, termimad::crossbeam::channel::bounded, }; fn compute_tree_status(root_path: &Path) -> ComputationResult { match Repository::open(root_path) { Ok(git_repo) => { let tree_git_status = time!(TreeGitStatus::from(&git_repo),); match tree_git_status { Some(gs) => ComputationResult::Done(gs), None => ComputationResult::None, } } Err(e) => { debug!("failed to discover repo: {:?}", e); ComputationResult::None } } } // the key is the path of the repository static TS_CACHE_MX: Lazy>>> = Lazy::new(|| Mutex::new(FxHashMap::default())); /// try to get the result of the computation of the tree git status. /// This may be immediate if a previous computation was finished. /// This may wait for the result of a new computation or of a previously /// launched one. /// In any case: /// - this function returns as soon as the dam asks for it (ie when there's an event) /// - computations are never dropped unless the program ends: they continue in background /// and the result may be available for following queries pub fn get_tree_status( root_path: &Path, dam: &mut Dam, ) -> ComputationResult { match git::closest_repo_dir(root_path) { None => ComputationResult::None, Some(repo_path) => { let comp = TS_CACHE_MX .lock() .unwrap() .get(&repo_path) .map(|c| (*c).clone()); match comp { Some(Computation::Finished(comp_res)) => { // already computed comp_res } Some(Computation::InProgress(comp_receiver)) => { // computation in progress // We do a select! to wait for either the dam // or the receiver debug!("start select on in progress computation"); dam.select(comp_receiver) } None => { // not yet started. We launch the computation and store // the receiver immediately. // We use the dam to return from this function when // needed (while letting the underlying thread finish // the job) // // note: must also update the TS_CACHE entry at end let (s, r) = bounded(1); TS_CACHE_MX .lock() .unwrap() .insert(repo_path.clone(), Computation::InProgress(r)); dam.try_compute(move || { let comp_res = compute_tree_status(&repo_path); TS_CACHE_MX .lock() .unwrap() .insert(repo_path.clone(), Computation::Finished(comp_res.clone())); if let Err(e) = s.send(comp_res.clone()) { debug!("error while sending comp result: {:?}", e); } comp_res }) } } } } } /// clear the finished or in progress computation. /// Limit: we may receive in cache the result of a computation /// which started before the clear (if this is a problem we could /// store a cleaning counter alongside the cache to prevent insertions) pub fn clear_status_computer_cache() { let mut ts_cache = TS_CACHE_MX.lock().unwrap(); ts_cache.clear(); } broot-1.46.3/src/help/help_content.rs000064400000000000000000000044661046102023000156250ustar 00000000000000use { termimad::minimad::{TextTemplate, TextTemplateExpander}, }; static MD: &str = r" # broot ${version} **broot** lets you explore directory trees and launch commands. It's best used when launched as **br**. See **https://dystroy.org/broot** for a complete guide. The *esc* key gets you back to the previous state. The *↑* and *↓* arrow keys can be used to change selection. The mouse can be used to select (on click) or open (on double-click). ## Search Modes Type some letters to search the tree and select the most relevant file. ${default-search For example, ${default-search-example}. } Various types of search can be used: |:-:|:-:|:- |**prefix**|**search**|**example**| |-:|:-|:- ${search-mode-rows |`${search-prefix}`|${search-type}|${search-example} } |- You can combine searches with logical operators. For example, to search all toml or rs files containing `tomat`, you may type `(${nr-prefix}toml/|${nr-prefix}rs$/)&${ce-prefix}tomat`. For efficiency, place content search last. ## Verbs To execute a verb, type a space or `:` then start of its name or shortcut. This table is searchable. Hit a few letters to filter it. |:-:|:-:|:-:|:-: |**name**|**shortcut**|**key**|**description** |-:|:-:|:-:|:- ${verb-rows |${name}|${shortcut}|${key}|${description}`${execution}` } |-: ## Configuration Verbs, skin, and more, are configured in ${config-files * **${path}** } (hit *enter* to open the main configuration file) ## Launch Arguments Some options can be set on launch: * `-h` or `--hidden` : show hidden files * `-i` : show files which are normally hidden due to .gitignore rules * `-d` or `--dates` : display last modified dates * `-w` : whale-spotting mode (for the complete list, run `broot --help`) ## Flags Flags are displayed at bottom right: * `h:y` or `h:n` : whether hidden files are shown * `gi:y`, `gi:n` : whether gitignore rules are active or not ## Special Features ${features-text} ${features * **${feature-name}:** ${feature-description} } "; /// build a markdown expander which will need to be /// completed with data and which then would be used to /// produce the markdown of the help page pub fn expander() -> TextTemplateExpander<'static, 'static> { use once_cell::sync::Lazy; static TEMPLATE: Lazy> = Lazy::new(|| TextTemplate::from(MD)); TEMPLATE.expander() } broot-1.46.3/src/help/help_features.rs000064400000000000000000000013151046102023000157570ustar 00000000000000 /// find the list of optional features which are enabled pub fn list() -> Vec<(&'static str, &'static str)> { #[allow(unused_mut)] let mut features: Vec<(&'static str, &'static str)> = Vec::new(); #[cfg(not(any(target_family = "windows", target_os = "android")))] features.push(("permissions", "allow showing file mode, owner and group")); #[cfg(feature = "clipboard")] features.push(( "clipboard", ":copy_path (copying the current path), and :input_paste (pasting into the input)", )); #[cfg(feature = "trash")] features.push(( "trash", ":trash, :open_trash, :restore_trashed_file, :purge_trash, :delete_trashed_file", )); features } broot-1.46.3/src/help/help_search_modes.rs000064400000000000000000000036271046102023000166050ustar 00000000000000use { crate::{ app::AppContext, pattern::*, }, }; /// what should be shown for a search_mode in the help screen, after /// filtering pub struct SearchModeHelp { pub prefix: String, pub description: String, pub example: String, } /// return the rows of the "Search Modes" table in help. pub fn search_mode_help(mode: SearchMode, con: &AppContext) -> SearchModeHelp { let prefix = mode.prefix(con); let description = format!( "{} search on {}", match mode.kind() { SearchKind::Exact => "exact string", SearchKind::Fuzzy => "fuzzy", SearchKind::Regex => "regex", SearchKind::Tokens => "tokens", }, match mode.object() { SearchObject::Name => "file name", SearchObject::Path => "sub path", SearchObject::Content => "file content", }, ); let example = match mode { SearchMode::NameExact => format!("`{prefix}feat` matches *help_features.rs*"), SearchMode::NameFuzzy => format!("`{prefix}conh` matches *DefaultConf.hjson*"), SearchMode::NameRegex => format!("`{prefix}rs$` matches *build.rs*"), SearchMode::NameTokens => format!("`{prefix}fea,he` matches *HelpFeature.java*"), SearchMode::PathExact => format!("`{prefix}te\\/do` matches *website/docs*"), SearchMode::PathFuzzy => format!("`{prefix}flam` matches *src/flag/mod.rs*"), SearchMode::PathRegex => format!(r#"`{prefix}\d{{3}}.*txt` matches *dir/a123/b.txt*"#), SearchMode::PathTokens => format!("`{prefix}help,doc` matches *website/docs/help.md*"), SearchMode::ContentExact => format!("`{prefix}find(` matches a file containing *a.find(b);*"), SearchMode::ContentRegex => format!("`{prefix}find/i` matches a file containing *A::Find(b)*"), }; SearchModeHelp { prefix, description, example, } } broot-1.46.3/src/help/help_state.rs000064400000000000000000000205431046102023000152650ustar 00000000000000use { super::{ help_content, SearchModeHelp, }, crate::{ app::*, command::{Command, TriggerType}, conf::Conf, display::{Screen, W}, errors::ProgramError, launchable::Launchable, pattern::*, tree::TreeOptions, verb::*, }, std::path::{Path, PathBuf}, termimad::{Area, FmtText, TextView}, }; /// an application state dedicated to help pub struct HelpState { pub scroll: usize, pub text_area: Area, dirty: bool, // background must be cleared pattern: Pattern, tree_options: TreeOptions, config_path: PathBuf, // the last config path when several were used mode: Mode, } impl HelpState { pub fn new( tree_options: TreeOptions, _screen: Screen, con: &AppContext, ) -> HelpState { let text_area = Area::uninitialized(); // will be fixed at drawing time let config_path = con.config_paths .first() .cloned() .unwrap_or_else(Conf::default_location); HelpState { text_area, scroll: 0, dirty: true, pattern: Pattern::None, tree_options, config_path, mode: con.initial_mode(), } } } impl PanelState for HelpState { fn get_type(&self) -> PanelStateType { PanelStateType::Help } fn set_mode(&mut self, mode: Mode) { self.mode = mode; } fn get_mode(&self) -> Mode { self.mode } fn selected_path(&self) -> Option<&Path> { Some(&self.config_path) } fn tree_options(&self) -> TreeOptions { self.tree_options.clone() } fn with_new_options( &mut self, _screen: Screen, change_options: &dyn Fn(&mut TreeOptions) -> &'static str, _in_new_panel: bool, // TODO open a tree if true _con: &AppContext, ) -> CmdResult { change_options(&mut self.tree_options); CmdResult::Keep } fn selection(&self) -> Option> { Some(Selection { path: &self.config_path, stype: SelectionType::File, is_exe: false, line: 0, }) } fn refresh(&mut self, _screen: Screen, _con: &AppContext) -> Command { self.dirty = true; Command::empty() } fn on_pattern( &mut self, pat: InputPattern, _app_state: &AppState, _con: &AppContext, ) -> Result { self.pattern = pat.pattern; Ok(CmdResult::Keep) } fn display( &mut self, w: &mut W, disc: &DisplayContext, ) -> Result<(), ProgramError> { let con = &disc.con; let mut text_area = disc.state_area.clone(); text_area.pad_for_max_width(120); if text_area != self.text_area { self.dirty = true; self.text_area = text_area; } if self.dirty { disc.panel_skin.styles.default.queue_bg(w)?; disc.screen.clear_area_to_right(w, &disc.state_area)?; self.dirty = false; } let mut expander = help_content::expander(); expander.set("version", env!("CARGO_PKG_VERSION")); let config_paths: Vec = con.config_paths.iter() .map(|p| p.to_string_lossy().to_string()) .collect(); for path in &config_paths { expander.sub("config-files") .set("path", path); } let verb_rows = super::help_verbs::matching_verb_rows(&self.pattern, con); for row in &verb_rows { let sub = expander .sub("verb-rows") .set_md("name", row.name()) .set_md("shortcut", row.shortcut()) .set("key", &row.keys_desc); if row.verb.description.code { sub.set("description", ""); sub.set("execution", &row.verb.description.content); } else { sub.set_md("description", &row.verb.description.content); sub.set("execution", ""); } } let mode_help; if let Ok(default_mode) = con.search_modes.search_mode(None) { mode_help = super::search_mode_help(default_mode, con); expander .sub("default-search") .set_md("default-search-example", &mode_help.example); } let search_rows: Vec = SEARCH_MODES .iter() .map(|mode| super::search_mode_help(*mode, con)) .collect(); for row in &search_rows { expander .sub("search-mode-rows") .set("search-prefix", &row.prefix) .set("search-type", &row.description) .set_md("search-example", &row.example); } let nr_prefix = SearchMode::NameRegex.prefix(con); let ce_prefix = SearchMode::ContentExact.prefix(con); expander .set("nr-prefix", &nr_prefix) .set("ce-prefix", &ce_prefix); let features = super::help_features::list(); expander.set( "features-text", if features.is_empty() { "This release was compiled with no optional feature enabled." } else { "This release was compiled with those optional features enabled:" }, ); for feature in &features { expander .sub("features") .set("feature-name", feature.0) .set("feature-description", feature.1); } let text = expander.expand(); let fmt_text = FmtText::from_text( &disc.panel_skin.help_skin, text, Some((self.text_area.width - 1) as usize), ); let mut text_view = TextView::from(&self.text_area, &fmt_text); self.scroll = text_view.set_scroll(self.scroll); Ok(text_view.write_on(w)?) } fn on_internal( &mut self, w: &mut W, invocation_parser: Option<&InvocationParser>, internal_exec: &InternalExecution, input_invocation: Option<&VerbInvocation>, trigger_type: TriggerType, app_state: &mut AppState, cc: &CmdContext, ) -> Result { use Internal::*; Ok(match internal_exec.internal { Internal::back => { if self.pattern.is_some() { self.pattern = Pattern::None; CmdResult::Keep } else { CmdResult::PopState } } help => CmdResult::Keep, line_down | line_down_no_cycle => { self.scroll += get_arg(input_invocation, internal_exec, 1); CmdResult::Keep } line_up | line_up_no_cycle => { let dy = get_arg(input_invocation, internal_exec, 1); self.scroll = if self.scroll > dy { self.scroll - dy } else { 0 }; CmdResult::Keep } open_stay => match opener::open(Conf::default_location()) { Ok(exit_status) => { info!("open returned with exit_status {:?}", exit_status); CmdResult::Keep } Err(e) => CmdResult::DisplayError(format!("{e:?}")), }, // FIXME check we can't use the generic one open_leave => { CmdResult::from(Launchable::opener( Conf::default_location() )) } page_down => { self.scroll += self.text_area.height as usize; CmdResult::Keep } page_up => { let height = self.text_area.height as usize; self.scroll = if self.scroll > height { self.scroll - self.text_area.height as usize } else { 0 }; CmdResult::Keep } _ => self.on_internal_generic( w, invocation_parser, internal_exec, input_invocation, trigger_type, app_state, cc, )?, }) } } broot-1.46.3/src/help/help_verbs.rs000064400000000000000000000045731046102023000152730ustar 00000000000000use { crate::{ app::AppContext, keys, pattern::*, verb::*, }, }; /// what should be shown for a verb in the help screen, after /// filtering pub struct MatchingVerbRow<'v> { name: Option, shortcut: Option, pub verb: &'v Verb, pub keys_desc: String, } impl MatchingVerbRow<'_> { /// the name in markdown (with matching chars in bold if /// some filtering occurred) pub fn name(&self) -> &str { // there should be a better way to write this self.name .as_deref() .unwrap_or_else(|| match self.verb.names.first() { Some(s) => s.as_str(), _ => " ", }) } pub fn shortcut(&self) -> &str { // there should be a better way to write this self.shortcut .as_deref() .unwrap_or_else(|| match self.verb.names.get(1) { Some(s) => s.as_str(), _ => " ", }) } } /// return the rows of the verbs table in help, taking the current filter /// into account pub fn matching_verb_rows<'v>( pat: &Pattern, con: &'v AppContext, ) -> Vec> { let mut rows = Vec::new(); for verb in con.verb_store.verbs() { if !verb.show_in_doc { continue; } let mut name = None; let mut shortcut = None; if pat.is_some() { let mut ok = false; name = verb.names.first().and_then(|s| { pat.search_string(s).map(|nm| { ok = true; nm.wrap(s, "**", "**") }) }); shortcut = verb.names.get(1).and_then(|s| { pat.search_string(s).map(|nm| { ok = true; nm.wrap(s, "**", "**") }) }); if !ok { continue; } } let keys_desc = verb .keys .iter() .filter(|&&k| { con.modal || !keys::is_key_only_modal(k) }) .map(|&k| keys::KEY_FORMAT.to_string(k)) .collect::>() // no way to join an iterator today ? .join(", "); rows.push(MatchingVerbRow { name, shortcut, keys_desc, verb, }); } rows } broot-1.46.3/src/help/mod.rs000064400000000000000000000002371046102023000137120ustar 00000000000000mod help_content; mod help_features; mod help_search_modes; mod help_state; mod help_verbs; pub use { help_state::HelpState, help_search_modes::*, }; broot-1.46.3/src/hex/byte.rs000064400000000000000000000030441046102023000137310ustar 00000000000000use { crate::{ skin::StyleMap, }, termimad::CompoundStyle, }; pub enum ByteCategory { Null, AsciiGraphic, AsciiWhitespace, AsciiOther, NonAscii, } #[derive(Clone, Copy)] pub struct Byte(u8); impl From for Byte { fn from(u: u8) -> Self { Self(u) } } impl Byte { pub fn category(self) -> ByteCategory { if self.0 == 0x00 { ByteCategory::Null } else if self.0.is_ascii_graphic() { ByteCategory::AsciiGraphic } else if self.0.is_ascii_whitespace() { ByteCategory::AsciiWhitespace } else if self.0.is_ascii() { ByteCategory::AsciiOther } else { ByteCategory::NonAscii } } pub fn style(self, styles: &StyleMap) -> &CompoundStyle { match self.category() { ByteCategory::Null => &styles.hex_null, ByteCategory::AsciiGraphic => &styles.hex_ascii_graphic, ByteCategory::AsciiWhitespace => &styles.hex_ascii_whitespace, ByteCategory::AsciiOther => &styles.hex_ascii_other, ByteCategory::NonAscii => &styles.hex_non_ascii, } } pub fn as_char(self) -> char { match self.category() { ByteCategory::Null => '0', ByteCategory::AsciiGraphic => self.0 as char, ByteCategory::AsciiWhitespace if self.0 == 0x20 => ' ', ByteCategory::AsciiWhitespace => '_', ByteCategory::AsciiOther => '•', ByteCategory::NonAscii => '×', } } } broot-1.46.3/src/hex/hex_view.rs000064400000000000000000000173331046102023000146120ustar 00000000000000use { super::byte::Byte, crate::{ command::ScrollCommand, display::{Screen, W}, errors::ProgramError, skin::PanelSkin, }, crokey::crossterm::{ cursor, style::{Color, Print, SetForegroundColor}, QueueableCommand, }, memmap2::Mmap, std::{ fs::File, io, path::PathBuf, }, termimad::{Area, CropWriter, SPACE_FILLING}, }; pub struct HexLine { pub bytes: Vec, // from 1 to 16 bytes } /// a preview showing the content of a file in hexa pub struct HexView { path: PathBuf, len: usize, scroll: usize, page_height: usize, } impl HexView { pub fn new(path: PathBuf) -> io::Result { let len = path.metadata()?.len() as usize; Ok(Self { path, len, scroll: 0, page_height: 0, }) } pub fn line_count(&self) -> usize { self.len / 16 + usize::from(self.len % 16 != 0) } pub fn try_scroll( &mut self, cmd: ScrollCommand, ) -> bool { let old_scroll = self.scroll; self.scroll = cmd.apply(self.scroll, self.line_count(), self.page_height); self.scroll != old_scroll } pub fn select_first(&mut self) { self.scroll = 0; } pub fn select_last(&mut self) { if self.page_height < self.line_count() { self.scroll = self.line_count() - self.page_height; } } pub fn get_page( &mut self, start_line_idx: usize, line_count: usize, ) -> io::Result> { let file = File::open(&self.path)?; let mut lines = Vec::new(); if self.len == 0 { return Ok(lines); } let mmap = unsafe { Mmap::map(&file)? }; let new_len = mmap.len(); if new_len != self.len { warn!("previewed file len changed from {} to {}", self.len, new_len); self.len = new_len; } let mut start_idx = 16 * start_line_idx; while start_idx < self.len { let line_len = 16.min(self.len - start_idx); let mut bytes: Vec = vec![0; line_len]; bytes[0..line_len].copy_from_slice(&mmap[start_idx..start_idx + line_len]); lines.push(HexLine { bytes }); if lines.len() >= line_count { break; } start_idx += line_len; } Ok(lines) } pub fn display( &mut self, w: &mut W, _screen: Screen, panel_skin: &PanelSkin, area: &Area, ) -> Result<(), ProgramError> { let line_count = area.height as usize; self.page_height = area.height as usize; let page = self.get_page(self.scroll, line_count)?; let addresses_len = if self.len < 0xffff { 4 } else if self.len < 0xff_ffff { 6 } else { 8 }; let mut margin_around_addresses = false; let styles = &panel_skin.styles; let mut left_margin = false; let mut addresses = false; let mut hex_middle_space = false; let mut chars_middle_space = false; let mut inter_hex = false; let mut chars = false; const MIN: i32 = 1 // margin + 32 // 32 hex + 1; // scrollbar let mut rem = area.width as i32 - MIN; if rem > 17 { chars = true; rem -= 17; } if rem > 16 { inter_hex = true; rem -= 16; } if rem > addresses_len { addresses = true; rem -= addresses_len; } if rem > 1 { hex_middle_space = true; rem -= 1; } if rem > 1 { left_margin = true; rem -= 1; } if rem > 1 { chars_middle_space = true; rem -= 1; } if addresses && rem >= 2 { margin_around_addresses = true; //rem -= 2; } let scrollbar = area.scrollbar(self.scroll, self.line_count()); let scrollbar_fg = styles.scrollbar_thumb.get_fg() .or_else(|| styles.preview.get_fg()) .unwrap_or(Color::White); for y in 0..line_count { w.queue(cursor::MoveTo(area.left, y as u16 + area.top))?; let mut cw = CropWriter::new(w, area.width as usize - 1); // -1 for scrollbar let cw = &mut cw; if y < page.len() { if addresses { let addr = (self.scroll + y) * 16; cw.queue_g_string( &styles.preview_line_number, match (addresses_len, margin_around_addresses) { (4, false) => format!("{addr:04x}"), (6, false) => format!("{addr:06x}"), (_, false) => format!("{addr:08x}"), (4, true) => format!(" {addr:04x} "), (6, true) => format!(" {addr:06x} "), (_, true) => format!(" {addr:08x} "), }, )?; } if left_margin { cw.queue_char(&styles.default, ' ')?; } let line = &page[y]; for x in 0..16 { if x == 8 && hex_middle_space { cw.queue_char(&styles.default, ' ')?; } if let Some(b) = line.bytes.get(x) { let byte = Byte::from(*b); if inter_hex { cw.queue_g_string(byte.style(styles), format!("{b:02x} "))?; } else { cw.queue_g_string(byte.style(styles), format!("{b:02x}"))?; } } else { cw.queue_str(&styles.default, if inter_hex { " " } else { " " })?; } } if chars { cw.queue_char(&styles.default, ' ')?; for x in 0..16 { if x == 8 && chars_middle_space { cw.queue_char(&styles.default, ' ')?; } if let Some(b) = line.bytes.get(x) { let byte = Byte::from(*b); cw.queue_char(byte.style(styles), byte.as_char())?; } } } } cw.fill(&styles.default, &SPACE_FILLING)?; if is_thumb(y as u16 + area.top, scrollbar) { w.queue(SetForegroundColor(scrollbar_fg))?; w.queue(Print('▐'))?; } else { w.queue(Print(' '))?; } } Ok(()) } pub fn display_info( &mut self, w: &mut W, _screen: Screen, panel_skin: &PanelSkin, area: &Area, ) -> Result<(), ProgramError> { let width = area.width as usize; let mut s = format!("{}", self.len); if s.len() > width { return Ok(()); } if s.len() + " bytes".len() < width { s = format!("{s} bytes"); } else if s.len() + 1 < width { s = format!("{s}b"); } w.queue(cursor::MoveTo( area.left + area.width - s.len() as u16, area.top, ))?; panel_skin.styles.default.queue(w, s)?; Ok(()) } } fn is_thumb(y: u16, scrollbar: Option<(u16, u16)>) -> bool { if let Some((sctop, scbottom)) = scrollbar { if sctop <= y && y <= scbottom { return true; } } false } broot-1.46.3/src/hex/mod.rs000064400000000000000000000000661046102023000135460ustar 00000000000000 mod byte; mod hex_view; pub use hex_view::HexView; broot-1.46.3/src/icon/font.rs000064400000000000000000000120041046102023000140740ustar 00000000000000use { super::*, crate::tree::TreeLineType, rustc_hash::FxHashMap, }; pub struct FontPlugin { icon_name_to_icon_codepoint_map: FxHashMap<&'static str, u32>, file_name_to_icon_name_map: FxHashMap<&'static str, &'static str>, double_extension_to_icon_name_map: FxHashMap<&'static str, &'static str>, extension_to_icon_name_map: FxHashMap<&'static str, &'static str>, default_icon_point: u32, } impl FontPlugin { #[cfg(debug_assertions)] fn sanity_check( part_to_icon_name_map: &FxHashMap<&str, &str>, icon_name_to_icon_codepoint_map: &FxHashMap<&str, u32>, ) { let offending_entries = part_to_icon_name_map .iter() .map(|(_k, icon_name)| { ( icon_name, icon_name_to_icon_codepoint_map.contains_key(icon_name), ) }) // Find if any entry is not present .filter(|(_entry, entry_present)| !entry_present) .collect::>(); for oe in &offending_entries { eprintln!("{} is not a valid icon name", oe.0); } if !offending_entries.is_empty() { eprintln!("Terminating execution"); std::process::exit(53); } } pub fn new( icon_name_to_icon_codepoint_map: &'static [(&'static str, u32)], double_extension_to_icon_name_map: &'static [(&'static str, &'static str)], extension_to_icon_name_map: &'static [(&'static str, &'static str)], file_name_to_icon_name_map: &'static [(&'static str, &'static str)], ) -> Self { let icon_name_to_icon_codepoint_map: FxHashMap<_, _> = icon_name_to_icon_codepoint_map.iter().cloned().collect(); let double_extension_to_icon_name_map: FxHashMap<_, _> = double_extension_to_icon_name_map.iter().cloned().collect(); let extension_to_icon_name_map: FxHashMap<_, _> = extension_to_icon_name_map.iter().cloned().collect(); let file_name_to_icon_name_map: FxHashMap<_, _> = file_name_to_icon_name_map.iter().cloned().collect(); #[cfg(debug_assertions)] { Self::sanity_check( &file_name_to_icon_name_map, &icon_name_to_icon_codepoint_map, ); Self::sanity_check( &double_extension_to_icon_name_map, &icon_name_to_icon_codepoint_map, ); Self::sanity_check( &extension_to_icon_name_map, &icon_name_to_icon_codepoint_map, ); } let default_icon_point = *icon_name_to_icon_codepoint_map.get("default_file").unwrap(); Self { icon_name_to_icon_codepoint_map, file_name_to_icon_name_map, double_extension_to_icon_name_map, extension_to_icon_name_map, default_icon_point, } } fn handle_single_extension( &self, ext: Option, ) -> &'static str { match ext { None => "default_file", Some(ref e) => match self.extension_to_icon_name_map.get(e as &str) { None => "default_file", Some(icon_name) => icon_name, }, } } fn handle_file( &self, name: &str, double_ext: Option, ext: Option, ) -> &'static str { match self.file_name_to_icon_name_map.get(name) { Some(icon_name) => icon_name, _ => self.handle_double_extension(double_ext, ext), } } fn handle_double_extension( &self, double_ext: Option, ext: Option, ) -> &'static str { match double_ext { None => self.handle_single_extension(ext), Some(ref de) => match self.double_extension_to_icon_name_map.get(de as &str) { None => self.handle_single_extension(ext), Some(icon_name) => icon_name, }, } } } impl IconPlugin for FontPlugin { fn get_icon( &self, tree_line_type: &TreeLineType, name: &str, double_ext: Option<&str>, ext: Option<&str>, ) -> char { let icon_name = match tree_line_type { TreeLineType::Dir => "default_folder", TreeLineType::SymLink { .. } => "emoji_type_link", //bad but nothing better TreeLineType::File => self.handle_file( &name.to_ascii_lowercase(), double_ext.map(|de| de.to_ascii_lowercase()), ext.map(|e| e.to_ascii_lowercase()), ), TreeLineType::Pruning => "file_type_kite", //irrelevant _ => "default_file", }; let entry_icon = unsafe { std::char::from_u32_unchecked( *self .icon_name_to_icon_codepoint_map .get(icon_name) .unwrap_or(&self.default_icon_point), ) }; entry_icon } } broot-1.46.3/src/icon/icon_plugin.rs000064400000000000000000000003531046102023000154400ustar 00000000000000use { crate::tree::TreeLineType, }; pub trait IconPlugin { fn get_icon( &self, tree_line_type: &TreeLineType, name: &str, double_ext: Option<&str>, ext: Option<&str>, ) -> char; } broot-1.46.3/src/icon/mod.rs000064400000000000000000000021131046102023000137050ustar 00000000000000mod font; mod icon_plugin; use font::FontPlugin; pub use icon_plugin::IconPlugin; pub fn icon_plugin(icon_set: &str) -> Option> { match icon_set { "vscode" => Some(Box::new(FontPlugin::new( &include!("../../resources/icons/vscode/data/icon_name_to_icon_code_point_map.rs"), &include!("../../resources/icons/vscode/data/double_extension_to_icon_name_map.rs"), &include!("../../resources/icons/vscode/data/extension_to_icon_name_map.rs"), &include!("../../resources/icons/vscode/data/file_name_to_icon_name_map.rs"), ))), "nerdfont" => Some(Box::new(FontPlugin::new( &include!("../../resources/icons/nerdfont/data/icon_name_to_icon_code_point_map.rs"), &include!("../../resources/icons/nerdfont/data/double_extension_to_icon_name_map.rs"), &include!("../../resources/icons/nerdfont/data/extension_to_icon_name_map.rs"), &include!("../../resources/icons/nerdfont/data/file_name_to_icon_name_map.rs"), ))), _ => None, } } broot-1.46.3/src/image/double_line.rs000064400000000000000000000050361046102023000155500ustar 00000000000000use { crate::{ display::W, errors::ProgramError, }, ansi_colours, crokey::crossterm::{ style::{ Color, Colors, Print, SetColors, }, QueueableCommand, }, image::Rgba, termimad::fill_bg, }; const UPPER_HALF_BLOCK: char = '▀'; /// A "double line" normally contains two lines of pixels /// which are displayed as one line of characters, the /// UPPER_HALF_BLOCK foreground for the upper pixel and /// the background of the char for the lower pixel. /// It acts as a buffer which is dumped to screen when /// full or when the image is totally read. pub struct DoubleLine { img_width: usize, pixels: Vec, // size twice img_width true_colors: bool, } impl DoubleLine { pub fn new(img_width: usize, true_colors: bool) -> Self { Self { img_width, pixels: Vec::with_capacity(2 * img_width), true_colors, } } pub fn push(&mut self, rgba: Rgba) { self.pixels.push( if self.true_colors { Color::Rgb { r: rgba[0], g: rgba[1], b: rgba[2], } } else { Color::AnsiValue(ansi_colours::ansi256_from_rgb(( rgba[0], rgba[1], rgba[2], ))) } ); } pub fn is_empty(&self) -> bool { self.pixels.is_empty() } pub fn is_full(&self) -> bool { self.pixels.len() == 2 * self.img_width } pub fn write( &mut self, w: &mut W, left_margin: usize, right_margin: usize, bg: Color, ) -> Result<(), ProgramError> { debug_assert!( self.pixels.len() == self.img_width || self.pixels.len() == 2 * self.img_width ); // we may have either one or two lines let simple = self.pixels.len() < 2 * self.img_width; fill_bg(w, left_margin, bg)?; for i in 0..self.img_width { let foreground_color = self.pixels[i]; let background_color = if simple { bg } else { self.pixels[i + self.img_width] }; w.queue(SetColors(Colors::new( foreground_color, background_color, )))?; w.queue(Print(UPPER_HALF_BLOCK))?; } fill_bg(w, right_margin, bg)?; self.pixels.clear(); Ok(()) } } broot-1.46.3/src/image/image_view.rs000064400000000000000000000140031046102023000153750ustar 00000000000000use { super::{ double_line::DoubleLine, SourceImage, }, crate::{ app::*, display::{Screen, W}, errors::ProgramError, kitty::{self, KittyImageId}, skin::PanelSkin, }, crokey::crossterm::{ cursor, style::{ Color, SetBackgroundColor, }, QueueableCommand, }, image::{ DynamicImage, GenericImageView, }, std::path::{Path, PathBuf}, termimad::{fill_bg, Area}, }; #[derive(Debug)] struct DrawingInfo { drawing_count: usize, area: Area, } impl DrawingInfo { pub fn follows_in_place(&self, previous: &DrawingInfo) -> bool { self.drawing_count == previous.drawing_count + 1 && self.area == previous.area } } /// an already resized image, with the dimensions it /// was computed for (which may be different from the /// dimensions we got) struct CachedImage { img: DynamicImage, target_width: u32, target_height: u32, } /// an imageview can display an image in the terminal with /// a ratio of one pixel per char in width. pub struct ImageView { path: PathBuf, source_img: SourceImage, display_img: Option, last_drawing: Option, kitty_image_id: Option, } impl ImageView { pub fn new(path: &Path) -> Result { let source_img = time!( "decode image", path, SourceImage::new(path)? ); Ok(Self { path: path.to_path_buf(), source_img, display_img: None, last_drawing: None, kitty_image_id: None, }) } pub fn is_png(&self) -> bool { match self.path.extension() { Some(ext) => ext == "png" || ext == "PNG", None => false, } } pub fn display( &mut self, w: &mut W, disc: &DisplayContext, area: &Area, ) -> Result<(), ProgramError> { let styles = &disc.panel_skin.styles; let bg_color = styles.preview.get_bg() .or_else(|| styles.default.get_bg()); let bg = bg_color.unwrap_or(Color::AnsiValue(238)); // we avoid drawing when we were just displayed // on the last drawing_count and the area is the same. let drawing_info = DrawingInfo { drawing_count: disc.count, area: area.clone(), }; let must_draw = self.last_drawing .as_ref() .map_or(true, |previous| !drawing_info.follows_in_place(previous)); if must_draw { debug!("image_view must be cleared"); } else { debug!("no need to clear image_view"); } self.last_drawing = Some(drawing_info); let mut kitty_manager = kitty::manager().lock().unwrap(); if !must_draw { if let Some(kitty_image_id) = self.kitty_image_id { // we tell the manager the images must be kept, otherwise // they would be erased at end of drawing, as obsolete kitty_manager.keep(kitty_image_id, disc.count); } return Ok(()); } self.kitty_image_id = kitty_manager .try_print_image(w, &self.source_img, &self.path, area, bg, disc.count, disc.con)?; if self.kitty_image_id.is_some() { return Ok(()); } let target_width = area.width as u32; let target_height = (area.height * 2) as u32; let cached = self .display_img .as_ref() .filter(|ci| ci.target_width == target_width && ci.target_height == target_height); let img = match cached { Some(ci) => &ci.img, None => { let img = time!( "resize image", self.source_img.fitting(target_width, target_height, bg_color), )?; self.display_img = Some(CachedImage { img, target_width, target_height, }); &self.display_img.as_ref().unwrap().img } }; let (width, height) = img.dimensions(); debug!("resized image dimensions: {},{}", width, height); debug_assert!(width <= area.width as u32); let mut double_line = DoubleLine::new(width as usize, disc.con.true_colors); let mut y = area.top; let img_top_offset = (area.height - (height / 2) as u16) / 2; for _ in 0..img_top_offset { w.queue(cursor::MoveTo(area.left, y))?; fill_bg(w, area.width as usize, bg)?; y += 1; } let margin = area.width as usize - width as usize; let left_margin = margin / 2; let right_margin = margin - left_margin; w.queue(cursor::MoveTo(area.left, y))?; for pixel in img.pixels() { double_line.push(pixel.2); if double_line.is_full() { double_line.write(w, left_margin, right_margin, bg)?; y += 1; w.queue(cursor::MoveTo(area.left, y))?; } } if !double_line.is_empty() { double_line.write(w, left_margin, right_margin, bg)?; y += 1; } w.queue(SetBackgroundColor(bg))?; for y in y..area.top + area.height { w.queue(cursor::MoveTo(area.left, y))?; fill_bg(w, area.width as usize, bg)?; } Ok(()) } pub fn display_info( &mut self, w: &mut W, _screen: Screen, panel_skin: &PanelSkin, area: &Area, ) -> Result<(), ProgramError> { let dim = self.source_img.dimensions(); let s = format!("{} x {}", dim.0, dim.1); if s.len() > area.width as usize { return Ok(()); } w.queue(cursor::MoveTo( area.left + area.width - s.len() as u16, area.top, ))?; panel_skin.styles.default.queue(w, s)?; Ok(()) } } broot-1.46.3/src/image/mod.rs000064400000000000000000000002051046102023000140370ustar 00000000000000mod double_line; mod image_view; mod source_image; mod svg; pub use { image_view::ImageView, source_image::SourceImage, }; broot-1.46.3/src/image/source_image.rs000064400000000000000000000047311046102023000157320ustar 00000000000000use { super::svg, crate::{ errors::ProgramError, }, image::{ DynamicImage, GenericImageView, ImageReader, imageops::FilterType, }, std::{ borrow::Cow, path::Path, }, termimad::{ coolor, crossterm::style::Color as CrosstermColor, }, }; // Max dimensions of the SVG image to render. A bigger size just makes it need // a little more memory and takes more time to render. There's no quality gain // in having this bigger than your screen pub const MAX_SVG_BITMAP_WIDTH: u32 = 1000; pub const MAX_SVG_BITMAP_HEIGHT: u32 = 1000; pub enum SourceImage { Bitmap(DynamicImage), Svg(resvg::usvg::Tree), } impl SourceImage { pub fn new(path: &Path) -> Result { let is_svg = matches!(path.extension(), Some(ext) if ext == "svg" || ext == "SVG"); let img = if is_svg { Self::Svg(svg::load(path)?) } else { Self::Bitmap(ImageReader::open(path)?.decode()?) }; Ok(img) } pub fn dimensions(&self) -> (u32, u32) { match self { Self::Bitmap(img) => img.dimensions(), Self::Svg(tree) => ( f32_to_u32(tree.size().width()), f32_to_u32(tree.size().height()) ) } } pub fn fitting( &self, mut max_width: u32, mut max_height: u32, bg_color: Option, ) -> Result { let img = match self { Self::Bitmap(img) => { let dim = self.dimensions(); max_width = max_width.min(dim.0); max_height = max_height.min(dim.1); img.resize(max_width, max_height, FilterType::Triangle) } Self::Svg(tree) => { let bg_color: Option = bg_color.map(|cc| cc.into()); svg::render_tree(tree, max_width, max_height, bg_color)? } }; Ok(img) } pub fn optimal(&self) -> Result, ProgramError> { let cow = match self { Self::Bitmap(img) => Cow::Borrowed(img), Self::Svg(tree) => Cow::Owned( svg::render_tree(tree, MAX_SVG_BITMAP_WIDTH, MAX_SVG_BITMAP_HEIGHT, None)? ), }; Ok(cow) } } fn f32_to_u32(v: f32) -> u32 { if v <= 0.0 || v >= u32::MAX as f32 { 0 } else { v as u32 } } broot-1.46.3/src/image/svg.rs000064400000000000000000000053751046102023000140740ustar 00000000000000use { crate::{ errors::SvgError, }, image::{ DynamicImage, RgbaImage, }, std::path::PathBuf, resvg::{ usvg, tiny_skia, }, termimad::coolor, }; fn compute_zoom(w:f32, h:f32, max_width:u32, max_height:u32) -> Result { let mw: f32 = max_width.max(2) as f32; let mh: f32 = max_height.max(2) as f32; let zoom = 1.0f32 .min(mw / w) .min(mh / h); if zoom > 0.0f32 { Ok(zoom) } else { Err(SvgError::Internal { message: "invalid SVG dimensions" }) } } pub fn load>( path: P, ) -> Result { let path: PathBuf = path.into(); let mut opt = usvg::Options { resources_dir: Some(path.clone()), ..Default::default() }; opt.fontdb_mut().load_system_fonts(); let svg_data = std::fs::read(path)?; let tree = usvg::Tree::from_data(&svg_data, &opt)?; Ok(tree) } pub fn render_tree( tree: &usvg::Tree, max_width: u32, max_height: u32, bg_color: Option, ) -> Result { let t_width = tree.size().width(); let t_height = tree.size().height(); debug!("SVG natural size: {t_width} x {t_height}"); let zoom = compute_zoom(t_width, t_height, max_width, max_height)?; debug!("svg rendering zoom: {zoom}"); let px_width = (t_width * zoom) as u32; let px_height = (t_height * zoom) as u32; if px_width == 0 || px_height == 0 { return Err(SvgError::Internal { message: "invalid SVG dimensions" }); }; debug!("px_size: ({px_width}, {px_height})"); let mut pixmap = tiny_skia::Pixmap::new( px_width, px_height, ).ok_or(SvgError::Internal { message: "unable to create pixmap buffer" })?; if let Some(bg_color) = bg_color { let rgb = bg_color.rgb(); let bg_color = tiny_skia::Color::from_rgba8(rgb.r, rgb.g, rgb.b, 255); pixmap.fill(bg_color); } resvg::render( tree, tiny_skia::Transform::from_scale(zoom, zoom), &mut pixmap.as_mut(), ); let image_buffer = RgbaImage::from_vec( pixmap.width(), pixmap.height(), pixmap.take(), ).ok_or(SvgError::Internal { message: "wrong image buffer size" })?; Ok(DynamicImage::ImageRgba8(image_buffer)) } /// Generate a bitmap at the natural dimensions of the SVG unless it's too big /// in which case a smaller one is generated to fit into (max_width x max_height). /// /// Background will be black. #[allow(dead_code)] pub fn render>( path: P, max_width: u32, max_height: u32, ) -> Result { let tree = load(path)?; let image = render_tree(&tree, max_width, max_height, None)?; Ok(image) } broot-1.46.3/src/keys.rs000064400000000000000000000012641046102023000131570ustar 00000000000000use { crokey::*, crossterm::event::{ KeyCode, KeyModifiers, }, once_cell::sync::Lazy, }; pub static KEY_FORMAT: Lazy = Lazy::new(|| { KeyCombinationFormat::default().with_lowercase_modifiers() }); pub fn is_reserved(key: KeyCombination) -> bool { key == key!(backspace) || key == key!(delete) || key == key!(esc) } /// Tell whether the key can only be used as a shortcut key if the /// modal mode is active. pub fn is_key_only_modal( key: KeyCombination, ) -> bool { matches!(key, KeyCombination { codes: OneToThree::One(KeyCode::Char(_)), modifiers: KeyModifiers::NONE | KeyModifiers::SHIFT, }) } broot-1.46.3/src/kitty/detect_support.rs000064400000000000000000000056771046102023000164300ustar 00000000000000use { cli_log::*, std::{ env, }, }; /// Determine whether Kitty's graphics protocol is supported /// by the terminal running broot. /// /// This is called only once, and cached in the KittyManager's /// MaybeRenderer state #[allow(unreachable_code)] pub fn is_kitty_graphics_protocol_supported() -> bool { debug!("is_kitty_graphics_protocol_supported ?"); #[cfg(not(unix))] { // because cell_size_in_pixels isn't implemented on Windows debug!("no kitty support yet on Windows"); return false; } // we detect Kitty by the $TERM or $TERMINAL env var for env_var in ["TERM", "TERMINAL"] { if let Ok(env_val) = env::var(env_var) { debug!("${} = {:?}", env_var, env_val); let env_val = env_val.to_ascii_lowercase(); if env_val.contains("kitty") { debug!(" -> this terminal seems to be Kitty"); return true; } } } // we detect Wezterm with the $TERM_PROGRAM env var and we // check its version to be sure it's one with support if let Ok(term_program) = env::var("TERM_PROGRAM") { debug!("$TERM_PROGRAM = {:?}", term_program); if term_program == "WezTerm" { if let Ok(version) = env::var("TERM_PROGRAM_VERSION") { debug!("$TERM_PROGRAM_VERSION = {:?}", version); if &*version < "20220105-201556-91a423da" { debug!("WezTerm's version predates Kitty Graphics protocol support"); } else { debug!("this looks like a compatible version"); return true; } } else { warn!("$TERM_PROGRAM_VERSION unexpectedly missing"); } } else if term_program == "ghostty" { debug!("Ghostty implements Kitty Graphics protocol"); return true; } } // Checking support with a proper CSI sequence should be the preferred way but // it doesn't work reliably on wezterm and requires a wait on other terminals. // As both Kitty and WezTerm set env vars allowing an easy detection, this // CSI based querying isn't necessary right now. // This feature is kept gated and should only be tried if other terminals // appear and can't be detected without CSI sequence. #[cfg(feature = "kitty-csi-check")] { let start = std::time::Instant::now(); const TIMEOUT_MS: u64 = 200; let response = xterm_query::query_osc( "\x1b_Gi=31,s=1,v=1,a=q,t=d,f=24;AAAA\x1b\\\x1b[c", TIMEOUT_MS, ); let s = match response { Err(e) => { debug!("xterm querying failed: {}", e); false } Ok(response) => response == "_Gi=31;OK" }; debug!("Xterm querying took {:?}", start.elapsed()); debug!("kitty protocol support: {:?}", s); return s; } false } broot-1.46.3/src/kitty/image_renderer.rs000064400000000000000000000265331046102023000163260ustar 00000000000000use { super::detect_support::is_kitty_graphics_protocol_supported, base64::{ engine::general_purpose::STANDARD as BASE64, Engine, }, crate::{ display::{ cell_size_in_pixels, W, }, errors::ProgramError, }, base64, cli_log::*, crokey::crossterm::{ cursor, QueueableCommand, style::Color, }, image::{ DynamicImage, GenericImageView, RgbImage, RgbaImage, }, lru::LruCache, rustc_hash::FxBuildHasher, serde::Deserialize, std::{ fs::File, io::{self, Write}, num::NonZeroUsize, path::{Path, PathBuf}, }, tempfile, termimad::{fill_bg, Area}, }; /// How to send the image to kitty /// /// Note that I didn't test yet the named shared memory /// solution offered by kitty. /// /// Documentation: /// https://sw.kovidgoyal.net/kitty/graphics-protocol/#the-transmission-medium #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Deserialize)] #[serde(rename_all = "snake_case")] pub enum TransmissionMedium { /// write a temp file, then give its path to kitty /// in the payload of the escape sequence. It's quite /// fast on SSD but a big downside is that it doesn't /// work if you're distant #[default] TempFile, /// send the whole rgb or rgba data, encoded in base64, /// in the payloads of several escape sequence (each one /// containing at most 4096 bytes). Works if broot runs /// on remote. Chunks, } #[derive(Debug, Clone)] pub struct KittyImageRendererOptions { pub transmission_medium: TransmissionMedium, pub kept_temp_files: NonZeroUsize, } enum ImageData<'i> { RgbRef(&'i RgbImage), RgbaRef(&'i RgbaImage), Rgb(RgbImage), } impl<'i> From<&'i DynamicImage> for ImageData<'i> { fn from(img: &'i DynamicImage) -> Self { if let Some(rgb) = img.as_rgb8() { debug!("using rgb"); Self::RgbRef(rgb) } else if let Some(rgba) = img.as_rgba8() { debug!("using rgba"); Self::RgbaRef(rgba) } else { debug!("converting to rgb8"); Self::Rgb(img.to_rgb8()) } } } impl ImageData<'_> { fn kitty_format(&self) -> &'static str { match self { Self::RgbaRef(_) => "32", _ => "24", } } fn bytes(&self) -> &[u8] { match self { Self::RgbRef(img) => img.as_raw(), Self::RgbaRef(img) => img.as_raw(), Self::Rgb(img) => img.as_raw(), } } } /// The max size of a data payload in a kitty escape sequence /// according to kitty's documentation const CHUNK_SIZE: usize = 4096; fn div_ceil(a: u32, b: u32) -> u32 { a / b + (0 != a % b) as u32 } /// The image renderer, with knowledge of the console cells /// dimensions, and built only on a compatible terminal #[derive(Debug)] pub struct KittyImageRenderer { cell_width: u32, cell_height: u32, next_id: usize, options: KittyImageRendererOptions, /// paths of temp files which have been written, with key /// being the input image path temp_files: LruCache, } /// An image prepared for a precise area on screen struct KittyImage<'i> { id: usize, data: ImageData<'i>, img_width: u32, img_height: u32, area: Area, } impl<'i> KittyImage<'i> { fn new<'r>( src: &'i DynamicImage, available_area: &Area, renderer: &'r mut KittyImageRenderer, ) -> Self { let (img_width, img_height) = src.dimensions(); let area = renderer.rendering_area(img_width, img_height, available_area); let data = src.into(); let id = renderer.new_id(); Self { id, data, img_width, img_height, area, } } /// Render the image by sending multiple kitty escape sequences, each /// one with part of the image raw data (encoded as base64) fn print_with_chunks( &self, w: &mut W, ) -> Result<(), ProgramError> { let encoded = BASE64.encode(self.data.bytes()); w.queue(cursor::MoveTo(self.area.left, self.area.top))?; let mut pos = 0; loop { if pos + CHUNK_SIZE < encoded.len() { write!( w, "\u{1b}_Ga=T,f={},t=d,i={},s={},v={},c={},r={},m=1;{}\u{1b}\\", self.data.kitty_format(), self.id, self.img_width, self.img_height, self.area.width, self.area.height, &encoded[pos..pos + CHUNK_SIZE], )?; pos += CHUNK_SIZE; } else { // last chunk write!(w, "\u{1b}_Gm=0;{}\u{1b}\\", &encoded[pos..encoded.len()],)?; break; } } Ok(()) } /// Render the image by writing the raw data in a temporary file /// then giving to kitty the path to this file in the payload of /// a unique kitty ecape sequence pub fn print_with_temp_file( &self, w: &mut W, temp_file: Option, // if None, no need to write it temp_file_path: &Path, ) -> Result<(), ProgramError> { if let Some(mut temp_file) = temp_file { temp_file.write_all(self.data.bytes())?; temp_file.flush()?; debug!("file len: {}", temp_file.metadata().unwrap().len()); } let path = temp_file_path.to_str() .ok_or_else(|| io::Error::new( io::ErrorKind::Other, "Path can't be converted to UTF8", ))?; let encoded_path = BASE64.encode(path); debug!("temp file written: {:?}", path); w.queue(cursor::MoveTo(self.area.left, self.area.top))?; write!( w, "\u{1b}_Ga=T,f={},t=t,i={},s={},v={},c={},r={};{}\u{1b}\\", self.data.kitty_format(), self.id, self.img_width, self.img_height, self.area.width, self.area.height, encoded_path, )?; Ok(()) } } impl KittyImageRenderer { /// Called only once (at most) by the KittyManager pub fn new( options: KittyImageRendererOptions, ) -> Option { if !is_kitty_graphics_protocol_supported() { return None; } let hasher = FxBuildHasher; let temp_files = LruCache::with_hasher(options.kept_temp_files, hasher); cell_size_in_pixels() .ok() .map(|(cell_width, cell_height)| Self { cell_width, cell_height, next_id: 1, options, temp_files, }) } pub fn delete_temp_files(&mut self) { for (_, temp_file_path) in self.temp_files.into_iter() { debug!("removing temp file: {:?}", temp_file_path); if let Err(e) = std::fs::remove_file(temp_file_path) { error!("failed to remove temp file: {:?}", e); } } } /// return a new image id fn new_id(&mut self) -> usize { let new_id = self.next_id; self.next_id += 1; new_id } /// Clean the area, then print the dynamicImage and /// return the KittyImageId for later removal from screen pub fn print( &mut self, w: &mut W, src: &DynamicImage, src_path: &Path, area: &Area, bg: Color, ) -> Result { // clean the background below (and around) the image for y in area.top..area.top + area.height { w.queue(cursor::MoveTo(area.left, y))?; fill_bg(w, area.width as usize, bg)?; } let img = KittyImage::new(src, area, self); debug!("transmission medium: {:?}", self.options.transmission_medium); w.flush()?; match self.options.transmission_medium { TransmissionMedium::TempFile => { let temp_file_key = format!( "{:?}-{}x{}", src_path, img.img_width, img.img_height, ); let mut old_path = None; if let Some(cached_path) = self.temp_files.pop(&temp_file_key) { if cached_path.exists() { old_path = Some(cached_path); } } let temp_file_path = if let Some(temp_file_path) = old_path { // the temp file is still there img.print_with_temp_file(w, None, &temp_file_path)?; temp_file_path } else { // either the temp file itself has been removed (unlikely), the temp // cache entry has been removed, or we just never viewed this image // with this size before let (temp_file, path) = tempfile::Builder::new() .prefix("broot-img-preview") .tempfile()? .keep() .map_err(|_| io::Error::new( io::ErrorKind::Other, "temp file can't be kept", ))?; img.print_with_temp_file(w, Some(temp_file), &path)?; path }; if let Some((_, old_path)) = self.temp_files.push(temp_file_key, temp_file_path) { debug!("removing temp file: {:?}", &old_path); if let Err(e) = std::fs::remove_file(&old_path) { error!("failed to remove temp file: {:?}", e); } } } TransmissionMedium::Chunks => img.print_with_chunks(w)?, } Ok(img.id) } fn rendering_area( &self, img_width: u32, img_height: u32, area: &Area, ) -> Area { let area_cols: u32 = area.width.into(); let area_rows: u32 = area.height.into(); let rdim = self.rendering_dim(img_width, img_height, area_cols, area_rows); Area::new( area.left + ((area_cols - rdim.0) / 2) as u16, area.top + ((area_rows - rdim.1) / 2) as u16, rdim.0 as u16, rdim.1 as u16, ) } fn rendering_dim( &self, img_width: u32, img_height: u32, area_cols: u32, area_rows: u32, ) -> (u32, u32) { let optimal_cols = div_ceil(img_width, self.cell_width); let optimal_rows = div_ceil(img_height, self.cell_height); debug!("area: {:?}", (area_cols, area_rows)); debug!("optimal: {:?}", (optimal_cols, optimal_rows)); if optimal_cols <= area_cols && optimal_rows <= area_rows { // no constraint (TODO center?) (optimal_cols, optimal_rows) } else if optimal_cols * area_rows > optimal_rows * area_cols { // we're constrained in width debug!("constrained in width"); (area_cols, optimal_rows * area_cols / optimal_cols) } else { // we're constrained in height debug!("constrained in height"); (optimal_cols * area_rows / optimal_rows, area_rows) } } } broot-1.46.3/src/kitty/mod.rs000064400000000000000000000076611046102023000141360ustar 00000000000000mod detect_support; mod image_renderer; pub use { image_renderer::*, }; use { crate::{ app::AppContext, display::W, errors::ProgramError, image::SourceImage, }, crokey::crossterm::style::Color, once_cell::sync::Lazy, std::{ io::Write, path::Path, sync::Mutex, }, termimad::Area, }; pub type KittyImageId = usize; static MANAGER: Lazy> = Lazy::new(|| { let manager = KittyManager { rendered_images: Vec::new(), renderer: MaybeRenderer::Untested, }; Mutex::new(manager) }); pub fn manager() -> &'static Mutex { &MANAGER } #[derive(Debug)] pub struct KittyManager { rendered_images: Vec, renderer: MaybeRenderer, } #[derive(Debug)] struct RenderedImage { image_id: KittyImageId, drawing_count: usize, } #[derive(Debug)] enum MaybeRenderer { Untested, Disabled, Enabled { renderer: KittyImageRenderer, }, } impl KittyManager { /// return the renderer if it's already checked and enabled, none if /// it's disabled or if it hasn't been tested yet pub fn renderer_if_tested(&mut self) -> Option<&mut KittyImageRenderer> { match &mut self.renderer { MaybeRenderer::Enabled { renderer } => Some(renderer), _ => None, } } pub fn delete_temp_files(&mut self) { if let MaybeRenderer::Enabled { renderer } = &mut self.renderer { renderer.delete_temp_files(); } } pub fn renderer( &mut self, con: &AppContext, ) -> Option<&mut KittyImageRenderer> { if matches!(self.renderer, MaybeRenderer::Disabled) { return None; } if matches!(self.renderer, MaybeRenderer::Enabled { .. }) { return self.renderer_if_tested(); } let options = KittyImageRendererOptions { transmission_medium: con.kitty_graphics_transmission, kept_temp_files: con.kept_kitty_temp_files, }; match KittyImageRenderer::new(options) { Some(renderer) => { self.renderer = MaybeRenderer::Enabled { renderer }; self.renderer_if_tested() } None => { self.renderer = MaybeRenderer::Disabled; None } } } pub fn keep( &mut self, kept_id: KittyImageId, drawing_count: usize, ) { for image in &mut self.rendered_images { if image.image_id == kept_id { image.drawing_count = drawing_count; } } } #[allow(clippy::too_many_arguments)] // yes, I know pub fn try_print_image( &mut self, w: &mut W, src: &SourceImage, src_path: &Path, // used to build a cache key area: &Area, bg: Color, drawing_count: usize, con: &AppContext, ) -> Result, ProgramError> { if let Some(renderer) = self.renderer(con) { let img = src.optimal()?; let new_id = renderer.print(w, &img, src_path, area, bg)?; self.rendered_images.push(RenderedImage { image_id: new_id, drawing_count, }); Ok(Some(new_id)) } else { Ok(None) } } pub fn erase_images_before( &mut self, w: &mut W, drawing_count: usize, ) -> Result<(), ProgramError> { let mut kept_images = Vec::new(); for image in self.rendered_images.drain(..) { if image.drawing_count >= drawing_count { kept_images.push(image); } else { let id = image.image_id; debug!("erase kitty image {}", id); write!(w, "\u{1b}_Ga=d,d=I,i={id}\u{1b}\\")?; } } self.rendered_images = kept_images; Ok(()) } } broot-1.46.3/src/launchable.rs000064400000000000000000000156351046102023000143110ustar 00000000000000use { crate::{ app::AppContext, display::{ DisplayableTree, Screen, W, }, errors::ProgramError, skin::{ ExtColorMap, StyleMap, }, tree::Tree, }, crokey::crossterm::{ cursor, event::{DisableMouseCapture, EnableMouseCapture}, terminal::{self, EnterAlternateScreen, LeaveAlternateScreen}, QueueableCommand, }, opener, std::{ env, io::{self, Write}, path::PathBuf, process::Command, }, which::which, }; /// description of a possible launch of an external program /// A launchable can only be executed on end of life of broot. #[derive(Debug)] pub enum Launchable { /// just print something on stdout on end of broot Printer { to_print: String, }, /// print the tree on end of broot TreePrinter { tree: Box, skin: Box, ext_colors: ExtColorMap, width: u16, height: u16, }, /// execute an external program Program { exe: String, args: Vec, working_dir: Option, switch_terminal: bool, capture_mouse: bool, keyboard_enhanced: bool, }, /// open a path SystemOpen { path: PathBuf, }, } /// If a part starts with a '$', replace it by the environment variable of the same name. /// This part is split too (because of https://github.com/Canop/broot/issues/114) fn resolve_env_variables(parts: Vec) -> Vec { let mut resolved = Vec::new(); for part in parts.into_iter() { if let Some(var_name) = part.strip_prefix('$') { if let Ok(val) = env::var(var_name) { resolved.extend(val.split(' ').map(|s| s.to_string())); continue; } if var_name == "EDITOR" { debug!("Env var $EDITOR not set, looking at editor command for fallback"); if let Ok(editor) = which("editor") { if let Some(editor) = editor.to_str() { debug!("Using editor solved as {editor:?}"); resolved.push(editor.to_string()); continue; } } } } resolved.push(part); } resolved } impl Launchable { pub fn opener(path: PathBuf) -> Launchable { Launchable::SystemOpen { path } } pub fn printer(to_print: String) -> Launchable { Launchable::Printer { to_print } } pub fn tree_printer( tree: &Tree, screen: Screen, style_map: StyleMap, ext_colors: ExtColorMap, ) -> Launchable { Launchable::TreePrinter { tree: Box::new(tree.clone()), skin: Box::new(style_map), ext_colors, width: screen.width, height: (tree.lines.len() as u16).min(screen.height - 1), } } pub fn program( parts: Vec, working_dir: Option, switch_terminal: bool, con: &AppContext, ) -> io::Result { let mut parts = resolve_env_variables(parts).into_iter(); match parts.next() { Some(exe) => Ok(Launchable::Program { exe, args: parts.collect(), working_dir, switch_terminal, capture_mouse: con.capture_mouse, keyboard_enhanced: con.keyboard_enhanced, }), None => Err(io::Error::new(io::ErrorKind::Other, "Empty launch string")), } } pub fn execute( &self, mut w: Option<&mut W>, ) -> Result<(), ProgramError> { match self { Launchable::Printer { to_print } => { println!("{to_print}"); Ok(()) } Launchable::TreePrinter { tree, skin, ext_colors, width, height } => { let dp = DisplayableTree::out_of_app(tree, skin, ext_colors, *width, *height); dp.write_on(&mut std::io::stdout()) } Launchable::Program { working_dir, switch_terminal, exe, args, capture_mouse, keyboard_enhanced, } => { debug!("working_dir: {working_dir:?}"); debug!("switch_terminal: {switch_terminal:?}"); if *switch_terminal { // we restore the normal terminal in case the executable // is a terminal application, and we'll switch back to // broot's alternate terminal when we're back to broot if let Some(ref mut w) = &mut w { if *keyboard_enhanced { crokey::pop_keyboard_enhancement_flags()?; } w.queue(cursor::Show).unwrap(); w.queue(LeaveAlternateScreen).unwrap(); if *capture_mouse { w.queue(DisableMouseCapture).unwrap(); } terminal::disable_raw_mode().unwrap(); w.flush().unwrap(); } } let mut old_working_dir = None; if let Some(working_dir) = working_dir { old_working_dir = std::env::current_dir().ok(); std::env::set_current_dir(working_dir).unwrap(); } let exec_res = Command::new(exe) .args(args.iter()) .spawn() .and_then(|mut p| p.wait()) .map_err(|source| ProgramError::LaunchError { program: exe.clone(), source, }); if *switch_terminal { if let Some(ref mut w) = &mut w { terminal::enable_raw_mode().unwrap(); if *capture_mouse { w.queue(EnableMouseCapture).unwrap(); } w.queue(EnterAlternateScreen).unwrap(); w.queue(cursor::Hide).unwrap(); w.flush().unwrap(); if *keyboard_enhanced { crokey::push_keyboard_enhancement_flags()?; } } } if let Some(old_working_dir) = old_working_dir { std::env::set_current_dir(old_working_dir).unwrap(); } exec_res?; // we trigger the error display after restoration Ok(()) } Launchable::SystemOpen { path } => { opener::open(path)?; Ok(()) } } } } broot-1.46.3/src/lib.rs000064400000000000000000000012541046102023000127510ustar 00000000000000#[macro_use] extern crate cli_log; pub mod app; pub mod browser; pub mod cli; pub mod command; pub mod conf; pub mod content_search; pub mod content_type; pub mod display; pub mod errors; pub mod file_sum; pub mod flag; pub mod git; pub mod hex; pub mod help; pub mod icon; pub mod image; pub mod keys; pub mod kitty; pub mod launchable; pub mod path; pub mod pattern; pub mod permissions; pub mod preview; pub mod print; pub mod stage; pub mod shell_install; pub mod skin; pub mod syntactic; pub mod task_sync; pub mod terminal; pub mod tree; pub mod tree_build; pub mod verb; #[cfg(unix)] pub mod filesystems; #[cfg(unix)] pub mod net; #[cfg(feature = "trash")] pub mod trash; broot-1.46.3/src/main.rs000064400000000000000000000013021046102023000131210ustar 00000000000000use cli_log::*; fn main() { init_cli_log!(); debug!("env::args(): {:#?}", std::env::args().collect::>()); match broot::cli::run() { Ok(Some(launchable)) => { debug!("launching {:#?}", launchable); if let Err(e) = launchable.execute(None) { warn!("Failed to launch {:?}", &launchable); warn!("Error: {:?}", e); eprintln!("{e}"); } } Ok(None) => {} Err(e) => { // this usually happens when the passed path isn't of a directory warn!("Error: {}", e); eprintln!("{e}"); } }; log_mem(Level::Info); info!("bye"); } broot-1.46.3/src/net/client.rs000064400000000000000000000022101046102023000142400ustar 00000000000000 use { super::{ Message, }, crate::{ errors::NetError, }, std::{ io::BufReader, os::unix::net::{ UnixStream, }, }, }; pub struct Client { path: String, } impl Client { pub fn new(socket_name: &str) -> Self { Self { path: super::socket_file_path(socket_name), } } pub fn send(&self, message: &Message) -> Result<(), NetError> { debug!("try connecting {:?}", &self.path); let mut stream = UnixStream::connect(&self.path)?; message.write(&mut stream)?; if let Message::GetRoot = message { // we wait for the answer let mut br = BufReader::new(&stream); match Message::read(&mut br) { Ok(answer) => { debug!("got an answer: {:?}", &answer); if let Message::Root(root) = answer { println!("{root}"); } } Err(e) => { warn!("got no answer but error {:?}", e); } } } Ok(()) } } broot-1.46.3/src/net/message.rs000064400000000000000000000035311046102023000144150ustar 00000000000000use { crate::{ errors::NetError, command::Sequence, }, std::{ io::{ self, BufRead, Write, }, }, }; /// A message which may be sent by a client or server to the other part #[derive(Debug)] pub enum Message { Command(String), Hi, GetRoot, Root(String), Sequence(Sequence), } fn read_line(r: &mut BR) -> Result { let mut line = String::new(); r.read_line(&mut line)?; debug!("read line => {:?}", &line); while line.ends_with('\n') || line.ends_with('\r') { line.pop(); } Ok(line) } impl Message { pub fn read(r: &mut BR) -> Result { // the first line gives the type of message match read_line(r)?.as_ref() { "CMD" => Ok(Self::Command(read_line(r)?)), "GET_ROOT" => Ok(Self::GetRoot), "ROOT" => Ok(Self::Root(read_line(r)?)), "SEQ" => Ok(Self::Sequence(Sequence::new( read_line(r)?, Some(read_line(r)?), ))), _ => Err(NetError::InvalidMessage), } } pub fn write(&self, w: &mut W) -> io::Result<()> { match self { Self::Command(c) => { writeln!(w, "CMD")?; writeln!(w, "{c}") } Self::GetRoot => { writeln!(w, "GET_ROOT") } Self::Hi => { writeln!(w, "HI") } Self::Root(path) => { writeln!(w, "ROOT")?; writeln!(w, "{path}") } Self::Sequence(Sequence { separator, raw }) => { writeln!(w, "SEQ")?; writeln!(w, "{raw}")?; writeln!(w, "{separator}") } } } } broot-1.46.3/src/net/mod.rs000064400000000000000000000003371046102023000135510ustar 00000000000000mod client; mod message; mod server; pub use { client::Client, message::Message, server::Server, }; pub fn socket_file_path(server_name: &str) -> String { format!("/tmp/broot-server-{server_name}.sock") } broot-1.46.3/src/net/server.rs000064400000000000000000000063751046102023000143100ustar 00000000000000use { super::Message, crate::{ errors::NetError, command::Sequence, }, std::{ fs, io::BufReader, os::unix::net::UnixListener, path::PathBuf, sync::{Arc, Mutex}, thread, }, termimad::crossbeam::channel::Sender, }; pub struct Server { path: String, } impl Server { pub fn new( name: &str, tx: Sender, root: Arc>, ) -> Result { let path = super::socket_file_path(name); if fs::metadata(&path).is_ok() { match fs::remove_file(&path) { Ok(_) => {} Err(e) => return Err(NetError::Io { source: e }), } } let listener = UnixListener::bind(&path)?; info!("listening on {}", &path); // we use only one thread as we don't want to support long connections thread::spawn(move || { for stream in listener.incoming() { match stream { Ok(mut stream) => { let mut br = BufReader::new(&stream); if let Some(sequence) = match Message::read(&mut br) { Ok(Message::Command(command)) => { debug!("got single command {:?}", &command); // we convert it to a sequence Some(Sequence::new_single(command)) } Ok(Message::GetRoot) => { debug!("got get root query"); let root = root.lock().unwrap(); let answer = Message::Root(root.to_string_lossy().to_string()); match answer.write(&mut stream) { Ok(()) => debug!("root path successfully returned"), Err(e) => warn!("error while answering: {:?}", e), } None } Ok(Message::Sequence(sequence)) => { debug!("got sequence {:?}", &sequence); Some(sequence) } Ok(message) => { debug!("got something not yet handled: {:?}", message); None } Err(e) => { warn!("Read error : {:?}", e); None } } { if let Err(e) = tx.send(sequence) { warn!("error while sending {:?}", e); return; } } } Err(e) => { warn!("Stream error : {:?}", e); } } } }); Ok(Self { path }) } } impl Drop for Server { fn drop(&mut self) { debug!("removing socket file"); fs::remove_file(&self.path).unwrap(); } } broot-1.46.3/src/path/anchor.rs000064400000000000000000000001431046102023000144050ustar 00000000000000#[derive(Debug, Clone, Copy)] pub enum PathAnchor { Unspecified, Parent, Directory, } broot-1.46.3/src/path/closest.rs000064400000000000000000000007131046102023000146120ustar 00000000000000use std::path::{Path, PathBuf}; /// return the closest enclosing directory pub fn closest_dir(mut path: &Path) -> PathBuf { loop { if path.exists() && path.is_dir() { return path.to_path_buf(); } match path.parent() { Some(parent) => path = parent, None => { debug!("no existing parent"); // unexpected return path.to_path_buf(); } } } } broot-1.46.3/src/path/common.rs000064400000000000000000000013701046102023000144260ustar 00000000000000use { std::path::{Components, PathBuf}, }; pub fn longest_common_ancestor(paths: &[PathBuf]) -> PathBuf { match paths.len() { 0 => PathBuf::new(), // empty 1 => paths[0].clone(), _ => { let cs0 = paths[0].components(); let mut csi: Vec = paths .iter() .skip(1) .map(|p| p.components()) .collect(); let mut lca = PathBuf::new(); for component in cs0 { for cs in &mut csi { if cs.next() != Some(component) { return lca; } } lca.push(component); } lca } } } broot-1.46.3/src/path/from.rs000064400000000000000000000057251046102023000141110ustar 00000000000000use { super::*, directories::UserDirs, rustc_hash::FxHashMap, lazy_regex::*, std::path::{Path, PathBuf}, }; pub static TILDE_REGEX: Lazy = lazy_regex!(r"^~(/|$)"); /// If the input has a tilde as first complete element, replace it /// with the user's home directory. Return the input as a path without /// transformation in other cases pub fn untilde(input: &str) -> PathBuf { PathBuf::from( &*TILDE_REGEX .replace(input, |c: &Captures| { if let Some(user_dirs) = UserDirs::new() { format!( "{}{}", user_dirs.home_dir().to_string_lossy(), &c[1], ) } else { warn!("no user dirs found, no expansion of ~"); c[0].to_string() } }) ) } /// Build a usable path from a user input which may be absolute /// (if it starts with / or ~) or relative to the supplied base_dir. /// (we might want to try detect windows drives in the future, too) pub fn path_from + std::fmt::Debug>( base_dir: P, anchor: PathAnchor, input: &str, ) -> PathBuf { if input.starts_with('/') { // if the input starts with a `/`, we use it as is input.into() } else if TILDE_REGEX.is_match(input) { // if the input starts with `~` as first token, we replace // this `~` with the user home directory untilde(input) } else { // we put the input behind the source (the selected directory // or its parent) and we normalize so that the user can type // paths with `../` let base_dir = match anchor { PathAnchor::Parent => base_dir .as_ref() .parent() .unwrap_or_else(|| base_dir.as_ref()) .to_path_buf(), _ => closest_dir(base_dir.as_ref()), }; normalize_path(base_dir.join(input)) } } pub fn path_str_from + std::fmt::Debug>( base_dir: P, input: &str, ) -> String { path_from(base_dir, PathAnchor::Unspecified, input) .to_string_lossy() .to_string() } /// Replace a group in the execution string, using /// data from the user input and from the selected line pub fn do_exec_replacement( ec: &Captures<'_>, replacement_map: &FxHashMap, ) -> String { let name = ec.get(1).unwrap().as_str(); if let Some(repl) = replacement_map.get(name) { if let Some(fmt) = ec.get(2) { match fmt.as_str() { "path-from-directory" => path_str_from(replacement_map.get("directory").unwrap(), repl), "path-from-parent" => path_str_from(replacement_map.get("parent").unwrap(), repl), _ => format!("invalid format: {:?}", fmt.as_str()), } } else { repl.to_string() } } else { format!("{{{name}}}") } } broot-1.46.3/src/path/mod.rs000064400000000000000000000003001046102023000137050ustar 00000000000000mod anchor; mod common; mod closest; mod from; mod normalize; mod special_path; pub use { anchor::*, closest::*, common::*, from::*, normalize::*, special_path::*, }; broot-1.46.3/src/path/normalize.rs000064400000000000000000000045061046102023000151420ustar 00000000000000use std::path::{Component, Path, PathBuf}; /// Improve the path to try remove and solve .. token. /// /// This assumes that `a/b/../c` is `a/c` which might be different from /// what the OS would have chosen when b is a link. This is OK /// for broot verb arguments but can't be generally used elsewhere /// (a more general solution would probably query the FS and just /// resolve b in case of links). /// /// This function ensures a given path ending with '/' still /// ends with '/' after normalization. pub fn normalize_path>(path: P) -> PathBuf { let ends_with_slash = path.as_ref() .to_str() .map_or(false, |s| s.ends_with('/')); let mut normalized = PathBuf::new(); for component in path.as_ref().components() { match &component { Component::ParentDir => { if !normalized.pop() { normalized.push(component); } } _ => { normalized.push(component); } } } if ends_with_slash { normalized.push(""); } normalized } #[cfg(test)] mod path_normalize_tests { use super::normalize_path; fn check(before: &str, after: &str) { println!("-----------------\nnormalizing {before:?}"); // As seen by Stargateur, the test here doesn't work on Windows // // There are two problems, at least: // // * strings used for test use the '/' separator. This is a test problem // * we do a "end with '/'" test in the tested function. This might // lead to suboptimal interaction on windows assert_eq!(normalize_path(before).to_string_lossy(), after); } #[test] fn test_path_normalization() { check("/abc/test/../thing.png", "/abc/thing.png"); check("/abc/def/../../thing.png", "/thing.png"); check("/home/dys/test", "/home/dys/test"); check("/home/dys", "/home/dys"); check("/home/dys/", "/home/dys/"); check("/home/dys/..", "/home"); check("/home/dys/../", "/home/"); check("/..", "/.."); check("../test", "../test"); check("/home/dys/../../../test", "/../test"); check("π/2", "π/2"); check( "/home/dys/dev/broot/../../../canop/test", "/home/canop/test", ); } } broot-1.46.3/src/path/special_path.rs000064400000000000000000000054111046102023000155720ustar 00000000000000use { glob, serde::Deserialize, std::path::Path, }; ///// Wrap a glob pattern to add the Deserialize trait //#[derive(Debug, Clone, PartialEq, Hash, Eq)] //pub struct Glob { // pattern: glob::Pattern, //} #[derive(Clone, Copy, Debug, Deserialize, Default, PartialEq)] pub struct SpecialHandling { #[serde(default)] pub show: Directive, #[serde(default)] pub list: Directive, #[serde(default)] pub sum: Directive, } #[derive(Clone, Debug, Copy, Deserialize, PartialEq, Eq, Default)] #[serde(rename_all = "snake_case")] pub enum Directive { #[default] Default, Never, Always, } #[derive(Debug, Clone)] pub struct SpecialPath { pub pattern: glob::Pattern, pub handling: SpecialHandling, } #[derive(Debug, Clone)] pub struct SpecialPaths { pub entries: Vec, } impl SpecialPaths { pub fn find>(&self, path: P) -> SpecialHandling { self .entries.iter() .find(|sp| sp.pattern.matches_path(path.as_ref())) .map(|sp| sp.handling) .unwrap_or_default() } pub fn show(&self, path: &Path) -> Directive { self.find(path).show } pub fn list(&self, path: &Path) -> Directive { self.find(path).list } pub fn sum(&self, path: &Path) -> Directive { self.find(path).sum } /// Add a special handling, if none was previously defined for that path pub fn add_default(&mut self, path: &str, handling: SpecialHandling) { if self.find(path) != Default::default() { return; } match glob::Pattern::new("/proc") { Ok(pattern) => { self.entries.push(SpecialPath { pattern, handling }); } Err(e) => { warn!("Invalid glob pattern: {path:?} : {e}"); } } } pub fn add_defaults(&mut self) { // see https://github.com/Canop/broot/issues/639 self.add_default("/proc", SpecialHandling { show: Directive::Default, list: Directive::Never, sum: Directive::Never, }); } /// Return a potentially smaller set of special paths, reduced /// to what can be in path pub fn reduce(&self, path: &Path) -> Self { let entries = self .entries .iter() .filter(|sp| sp.can_have_matches_in(path)) .cloned() .collect(); Self { entries } } } impl SpecialPath { pub fn new(pattern: glob::Pattern, handling: SpecialHandling) -> Self { Self { pattern, handling, } } pub fn can_have_matches_in(&self, path: &Path) -> bool { path.to_str() .map_or(false, |p| self.pattern.as_str().starts_with(p)) } } broot-1.46.3/src/pattern/candidate.rs000064400000000000000000000014251046102023000155740ustar 00000000000000use { crate::{ tree::TreeLine, }, std::{ path::Path, }, }; /// something which can be evaluated by a pattern to produce /// either a score or a more precise match #[derive(Debug, Clone, Copy)] pub struct Candidate<'c> { /// path to the file to open if the pattern searches into files pub path: &'c Path, /// path from the current root pub subpath: &'c str, /// filename pub name: &'c str, /// whether the file is regular (ie has a searchable content) pub regular_file: bool, } impl<'c> Candidate<'c> { pub fn from(line: &'c TreeLine) -> Self { Self { path: &line.path, subpath: &line.subpath, name: &line.name, regular_file: line.is_file(), } } } broot-1.46.3/src/pattern/composite_pattern.rs000064400000000000000000000223721046102023000174230ustar 00000000000000use { super::*, crate::content_search::ContentMatch, bet::*, smallvec::smallvec, std::path::Path, }; /// A pattern composing other ones with operators #[derive(Debug, Clone)] pub struct CompositePattern { pub expr: BeTree, } impl CompositePattern { pub fn new(expr: BeTree) -> Self { Self { expr } } pub fn score_of_string( &self, candidate: &str, ) -> Option { use PatternOperator::*; let composite_result: Option> = self.expr.eval( // score evaluation |pat| pat.score_of_string(candidate), // operator |op, a, b| { match (op, a, b) { (And, None, _) => None, // normally not called due to short-circuit (And, Some(sa), Some(Some(sb))) => Some(sa + sb), (Or, None, Some(Some(sb))) => Some(sb), (Or, Some(sa), Some(None)) => Some(sa), (Or, Some(sa), Some(Some(sb))) => Some(sa + sb), (Not, Some(_), _) => None, (Not, None, _) => Some(1), _ => None, } }, // short-circuit. We don't short circuit on 'or' because // we want to use both scores |op, a| match (op, a) { (And, None) => true, _ => false, }, ); composite_result.unwrap_or_else(|| { warn!("unexpectedly missing result "); None }) } pub fn score_of( &self, candidate: Candidate, ) -> Option { use PatternOperator::*; let composite_result: Option> = self.expr.eval( // score evaluation |pat| pat.score_of(candidate), // operator |op, a, b| { match (op, a, b) { (And, None, _) => None, // normally not called due to short-circuit (And, Some(sa), Some(Some(sb))) => Some(sa + sb), (Or, None, Some(Some(sb))) => Some(sb), (Or, Some(sa), Some(None)) => Some(sa), (Or, Some(sa), Some(Some(sb))) => Some(sa + sb), (Not, Some(_), _) => None, (Not, None, _) => Some(1), _ => None, } }, // short-circuit. We don't short circuit on 'or' because // we want to use both scores |op, a| match (op, a) { (And, None) => true, _ => false, }, ); composite_result.unwrap_or_else(|| { warn!("unexpectedly missing result "); None }) } pub fn search_string(&self, candidate: &str) -> Option { // an ideal algorithm would call score_of on patterns when the object is different // to deal with exclusions but I'll start today with something simpler use PatternOperator::*; let composite_result: Option> = self.expr.eval( // score evaluation |pat| pat.search_string(candidate), // operator |op, a, b| match (op, a, b) { (And, None, _) => None, // normally not called due to short-circuit (And, Some(sa), Some(Some(_))) => Some(sa), // we have to choose a match (Or, None, Some(Some(sb))) => Some(sb), (Or, Some(sa), Some(None)) => Some(sa), (Or, Some(sa), Some(Some(_))) => Some(sa), // we have to choose (Not, Some(_), _) => None, (Not, None, _) => { // this is quite arbitrary. Matching the whole string might be // costly for some use, so we match only the start Some(NameMatch { score: 1, pos: smallvec![0], }) } _ => None, }, |op, a| match (op, a) { (Or, Some(_)) => true, (And, None) => true, _ => false, }, ); // it's possible we didn't find a result because the composition composite_result .unwrap_or_else(||{ warn!("unexpectedly missing result "); None }) } pub fn search_content( &self, candidate: &Path, desired_len: usize, // available space for content match display ) -> Option { use PatternOperator::*; let composite_result: Option> = self.expr.eval( // score evaluation |pat| pat.search_content(candidate, desired_len), // operator |op, a, b| match (op, a, b) { (And, None, _) => None, // normally not called due to short-circuit (And, Some(sa), Some(Some(_))) => Some(sa), // we have to choose (Or, None, Some(Some(sb))) => Some(sb), (Or, Some(sa), Some(None)) => Some(sa), (Or, Some(sa), Some(Some(_))) => Some(sa), // we have to choose (Not, Some(_), _) => None, (Not, None, _) => { // We can't generate a content match for a whole file // content, so we build one of length 0. Some(ContentMatch { extract: "".to_string(), needle_start: 0, needle_end: 0, }) } _ => None, }, |op, a| match (op, a) { (Or, Some(_)) => true, (And, None) => true, _ => false, }, ); composite_result .unwrap_or_else(||{ warn!("unexpectedly missing result "); None }) } // Search for a string, trying to return a match (it's used when a // composite returns something but may be matching also on other parts // that we can't compute, like the content) pub fn find_string( &self, candidate: &str, ) -> Option { // an ideal algorithm would call score_of on patterns when the object is different // to deal with exclusions but I'll start today with something simpler use PatternOperator::*; let composite_result: Option> = self.expr.eval( // score evaluation |pat| pat.search_string(candidate), // operator |op, a, b| match (op, a, b) { (Not, Some(_), _) => None, (_, Some(ma), _) => Some(ma), (_, None, Some(omb)) => omb, _ => None, }, |op, a| match (op, a) { (Or, Some(_)) => true, _ => false, }, ); // it's possible we didn't find a result because the composition composite_result.unwrap_or_else(|| { warn!("unexpectedly missing result "); None }) } // Search for a string in content, trying to return a match as soon as some // part of the composite matches pub fn find_content( &self, candidate: &Path, desired_len: usize, // available space for content match display ) -> Option { use PatternOperator::*; let composite_result: Option> = self.expr.eval( // score evaluation |pat| pat.search_content(candidate, desired_len), // operator |op, a, b| match (op, a, b) { (Not, Some(_), _) => None, (_, Some(ma), _) => Some(ma), (_, None, Some(omb)) => omb, _ => None, }, |op, a| match (op, a) { (Or, Some(_)) => true, _ => false, }, ); composite_result.unwrap_or_else(|| { warn!("unexpectedly missing result "); None }) } pub fn get_match_line_count( &self, candidate: &Path, ) -> Option { use PatternOperator::*; let composite_result: Option> = self.expr.eval( // score evaluation |pat| pat.get_match_line_count(candidate), // operator |op, a, b| match (op, a, b) { (Not, Some(_), _) => None, (_, Some(ma), _) => Some(ma), (_, None, Some(omb)) => omb, _ => None, }, |op, a| match (op, a) { (Or, Some(_)) => true, _ => false, }, ); composite_result.unwrap_or_else(|| { warn!("unexpectedly missing result "); None }) } pub fn has_real_scores(&self) -> bool { self.expr.iter_atoms().fold(false, |r, p| match p { Pattern::NameFuzzy(_) | Pattern::PathFuzzy(_) => true, _ => r, }) } pub fn is_empty(&self) -> bool { let is_not_empty = self.expr.iter_atoms().any(|p| p.is_some()); !is_not_empty } } broot-1.46.3/src/pattern/content_pattern.rs000064400000000000000000000035141046102023000170700ustar 00000000000000 use { super::*, crate::{ content_search::*, }, std::{ fmt, path::Path, }, }; /// A pattern for searching in file content #[derive(Debug, Clone)] pub struct ContentExactPattern { needle: Needle, } impl fmt::Display for ContentExactPattern { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(self.as_str()) } } impl ContentExactPattern { pub fn new(pat: &str, max_file_size: usize) -> Self { Self { needle: Needle::new(pat, max_file_size) } } pub fn as_str(&self) -> &str { self.needle.as_str() } pub fn is_empty(&self) -> bool { self.needle.is_empty() } pub fn to_regex_parts(&self) -> (String, String) { (regex::escape(self.as_str()), "".to_string()) } pub fn score_of(&self, candidate: Candidate) -> Option { if !candidate.regular_file { return None; } match self.needle.search(candidate.path) { Ok(ContentSearchResult::Found { .. }) => Some(1), Ok(ContentSearchResult::NotFound) => None, Ok(ContentSearchResult::NotSuitable) => { None } Err(e) => { debug!("error while scanning {:?} : {:?}", &candidate.path, e); None } } } /// get the line of the first match, if any pub fn get_match_line_count( &self, path: &Path, ) -> Option { if let Ok(ContentSearchResult::Found { pos }) = self.needle.search(path) { line_count_at_pos(path, pos).ok() } else { None } } pub fn get_content_match( &self, path: &Path, desired_len: usize, ) -> Option { self.needle.get_match(path, desired_len) } } broot-1.46.3/src/pattern/content_regex_pattern.rs000064400000000000000000000062321046102023000202620ustar 00000000000000 use { super::*, crate::{ content_search::*, }, lazy_regex::regex, std::{ fmt, fs::File, io::{self, BufReader, BufRead}, path::Path, }, }; /// A regex for searching in file content #[derive(Debug, Clone)] pub struct ContentRegexPattern { rex: regex::Regex, flags: String, max_file_size: usize } impl fmt::Display for ContentRegexPattern { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "cr/{}/{}", self.rex, self.flags) } } impl ContentRegexPattern { pub fn new(pat: &str, flags: &str, max_file_size: usize) -> Result { Ok(Self { rex: super::build_regex(pat, flags)?, flags: flags.to_string(), max_file_size, }) } pub fn is_empty(&self) -> bool { self.rex.as_str().is_empty() } pub fn to_regex_parts(&self) -> (String, String) { (self.rex.to_string(), self.flags.clone()) } // TODO optimize with regex::bytes ? fn has_match(&self, path: &Path) -> io::Result { for line in BufReader::new(File::open(path)?).lines() { if self.rex.is_match(line?.as_str()) { return Ok(true); } } Ok(false) } pub fn score_of(&self, candidate: Candidate) -> Option { if !candidate.regular_file || !is_path_suitable(candidate.path, self.max_file_size) { return None; } match self.has_match(candidate.path) { Ok(true) => Some(1), Ok(false) => None, Err(e) => { debug!("error while scanning {:?} : {:?}", candidate.path, e); None } } } pub fn try_get_content_match( &self, path: &Path, desired_len: usize, ) -> io::Result> { for line in BufReader::new(File::open(path)?).lines() { let line = line?; if let Some(regex_match) = self.rex.find(line.as_str()) { return Ok(Some(ContentMatch::build( line.as_bytes(), regex_match.start(), regex_match.as_str(), desired_len, ))); } } Ok(None) } /// get the line of the first match, if any pub fn try_get_match_line_count( &self, path: &Path, ) -> io::Result> { let mut line_count = 1; for line in BufReader::new(File::open(path)?).lines() { let line = line?; if self.rex.is_match(line.as_str()) { return Ok(Some(line_count)); } line_count += 1; } Ok(None) } /// get the line of the first match, if any pub fn get_match_line_count( &self, path: &Path, ) -> Option { self.try_get_match_line_count(path) .unwrap_or(None) } pub fn get_content_match( &self, path: &Path, desired_len: usize, ) -> Option { self.try_get_content_match(path, desired_len).ok().flatten() } } broot-1.46.3/src/pattern/exact_pattern.rs000064400000000000000000000076111046102023000165240ustar 00000000000000//! a simple exact pattern matcher for filename filtering / sorting. //! It's not meant for file contents but for small strings (less than 1000 chars) //! such as file names. use { super::NameMatch, smallvec::SmallVec, std::{ fmt, fs::File, io::{self, BufRead, BufReader}, path::Path, }, }; // weights used in match score computing // (but we always take the leftist match) const BONUS_MATCH: i32 = 50_000; const BONUS_EXACT: i32 = 1_000; const BONUS_START: i32 = 10; const BONUS_START_WORD: i32 = 5; const BONUS_CANDIDATE_LENGTH: i32 = -1; // per byte const BONUS_DISTANCE_FROM_START: i32 = -1; // per byte /// A pattern for exact matching #[derive(Debug, Clone)] pub struct ExactPattern { pattern: String, chars_count: usize, } impl fmt::Display for ExactPattern { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.pattern.fmt(f) } } fn is_word_separator(c: u8) -> bool { matches!(c, b'_' | b' ' | b'-' | b'/') } impl ExactPattern { /// build a pattern which will later be usable for fuzzy search. /// A pattern should be reused pub fn from(pattern: &str) -> Self { Self { pattern: pattern.to_string(), chars_count: pattern.chars().count(), } } pub fn is_empty(&self) -> bool { self.chars_count == 0 } fn score(&self, start: usize, candidate: &str) -> i32 { // start is the byte index let mut score = BONUS_MATCH + BONUS_CANDIDATE_LENGTH * candidate.len() as i32; if start == 0 { score += BONUS_START; if candidate.len() == self.pattern.len() { score += BONUS_EXACT; } } else { if is_word_separator(candidate.as_bytes()[start - 1]) { score += BONUS_START_WORD; } score += BONUS_DISTANCE_FROM_START * start as i32; } score } /// return a match if the pattern can be found in the candidate string. pub fn find(&self, candidate: &str) -> Option { candidate.find(&self.pattern) .map(|start| { let score = self.score(start, candidate); // we must find the start in chars, not bytes for (char_idx, (byte_idx, _)) in candidate.char_indices().enumerate() { if byte_idx == start { let mut pos = SmallVec::with_capacity(self.chars_count); for i in 0..self.chars_count { pos.push(i + char_idx); } return NameMatch { score, pos, }; } } unreachable!(); // if there was a match, pos should have been reached }) } /// get the line of the first match, if any /// (not used today, we use content_pattern to search in files) pub fn try_get_match_line_count( &self, path: &Path, ) -> io::Result> { let mut line_count = 1; // first line in text editors is 1 for line in BufReader::new(File::open(path)?).lines() { let line = line?; if line.contains(&self.pattern) { return Ok(Some(line_count)); } line_count = 1; } Ok(None) } /// get the line of the first match, if any /// (not used today, we use content_pattern to search in files) pub fn get_match_line_count( &self, path: &Path, ) -> Option { self.try_get_match_line_count(path) .unwrap_or(None) } /// compute the score of the best match pub fn score_of(&self, candidate: &str) -> Option { candidate .find(&self.pattern) .map(|start| self.score(start, candidate)) } } broot-1.46.3/src/pattern/fuzzy_pattern.rs000064400000000000000000000307111046102023000166040ustar 00000000000000//! a fuzzy pattern matcher for filename filtering / sorting. //! It's not meant for file contents but for small strings (less than 1000 chars) //! such as file names. use { super::NameMatch, secular, smallvec::{smallvec, SmallVec}, std::fmt::{self, Write}, }; type CandChars = SmallVec<[char; 32]>; // weights used in match score computing const BONUS_MATCH: i32 = 50_000; const BONUS_EXACT: i32 = 1_000; const BONUS_START: i32 = 10; const BONUS_START_WORD: i32 = 5; const BONUS_CANDIDATE_LENGTH: i32 = -1; // per char const BONUS_MATCH_LENGTH: i32 = -10; // per char of length of the match const BONUS_NB_HOLES: i32 = -30; // there's also a max on that number const BONUS_SINGLED_CHAR: i32 = -15; // when there's a char, neither first not last, isolated /// A pattern for fuzzy matching #[derive(Debug, Clone)] pub struct FuzzyPattern { chars: Box<[char]>, // secularized characters max_nb_holes: usize, } impl fmt::Display for FuzzyPattern { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { for &c in self.chars.iter() { f.write_char(c)? } Ok(()) } } enum MatchSearchResult { Perfect(NameMatch), // no need to test other positions Some(NameMatch), None, } fn is_word_separator(c: char) -> bool { matches!(c, '_' | ' ' | '-') } impl FuzzyPattern { /// build a pattern which will later be usable for fuzzy search. /// A pattern should be reused pub fn from(pat: &str) -> Self { let chars = secular::normalized_lower_lay_string(pat) .chars() .collect::>() .into_boxed_slice(); let max_nb_holes = match chars.len() { 1 => 0, 2 => 1, 3 => 2, 4 => 2, 5 => 2, 6 => 3, 7 => 3, 8 => 4, _ => chars.len() * 4 / 7, }; FuzzyPattern { chars, max_nb_holes, } } /// an "empty" pattern is one which accepts everything because /// it has no discriminant pub fn is_empty(&self) -> bool { self.chars.is_empty() } fn tight_match_from_index( &self, cand_chars: &CandChars, start_idx: usize, // start index in candidate, in chars ) -> MatchSearchResult { let mut pos = smallvec![0; self.chars.len()]; // positions of matching chars in candidate let mut cand_idx = start_idx; let mut pat_idx = 0; // index both in self.chars and pos let mut in_hole = false; loop { if cand_chars[cand_idx] == self.chars[pat_idx] { pos[pat_idx] = cand_idx; if in_hole { // We're no more in a hole. // Let's look if we can bring back the chars before the hole let mut rev_idx = 1; loop { if pat_idx < rev_idx { break; } if cand_chars[cand_idx-rev_idx] == self.chars[pat_idx-rev_idx] { // we move the pos forward pos[pat_idx-rev_idx] = cand_idx-rev_idx; } else { break; } rev_idx += 1; } in_hole = false; } pat_idx += 1; if pat_idx == self.chars.len() { break; // match, finished } } else { // there's a hole if cand_chars.len() - cand_idx <= self.chars.len() - pat_idx { return MatchSearchResult::None; } in_hole = true; } cand_idx += 1; } let mut nb_holes = 0; let mut nb_singled_chars = 0; for idx in 1..pos.len() { if pos[idx] > 1 + pos[idx-1] { nb_holes += 1; if idx > 1 && pos[idx-1] > 1 + pos[idx-2] { // we improve a simple case: the one of a singleton which was created // by pushing forward a char if cand_chars[pos[idx-2]+1] == cand_chars[pos[idx-1]] { // in some cases we're really removing another singletons but // let's forget this pos[idx-1] = pos[idx-2]+1; nb_holes -= 1; } else { nb_singled_chars += 1; } } } } if nb_holes > self.max_nb_holes { return MatchSearchResult::None; } let match_len = 1 + cand_idx - pos[0]; let mut score = BONUS_MATCH; score += BONUS_CANDIDATE_LENGTH * (cand_chars.len() as i32); score += BONUS_SINGLED_CHAR * nb_singled_chars; score += BONUS_NB_HOLES * (nb_holes as i32); score += match_len as i32 * BONUS_MATCH_LENGTH; if pos[0] == 0 { score += BONUS_START + BONUS_START_WORD; if cand_chars.len() == self.chars.len() { score += BONUS_EXACT; return MatchSearchResult::Perfect(NameMatch { score, pos }); } } else { let previous = cand_chars[pos[0] - 1]; if is_word_separator(previous) { score += BONUS_START_WORD; if cand_chars.len() - pos[0] == self.chars.len() { return MatchSearchResult::Perfect(NameMatch { score, pos }); } } } MatchSearchResult::Some(NameMatch { score, pos }) } /// return a match if the pattern can be found in the candidate string. /// The algorithm tries to return the best one. For example if you search /// "abc" in "ababca-abc", the returned match would be at the end. pub fn find(&self, candidate: &str) -> Option { if candidate.len() < self.chars.len() { return None; } let mut cand_chars: CandChars = SmallVec::with_capacity(candidate.len()); cand_chars.extend(candidate.chars().map(secular::lower_lay_char)); if cand_chars.len() < self.chars.len() { return None; } let mut best_score = 0; let mut best_match: Option = None; let n = cand_chars.len() + 1 - self.chars.len(); for start_idx in 0..n { if cand_chars[start_idx] == self.chars[0] { match self.tight_match_from_index(&cand_chars, start_idx) { MatchSearchResult::Perfect(m) => { return Some(m); } MatchSearchResult::Some(m) => { if m.score > best_score { best_score = m.score; best_match = Some(m); } // we could make start_idx jump to pos[0] here // but it doesn't improve the perfs (it's rare // anyway to have pos[0] much greater than the // start of the search) } _ => {} } } } best_match } /// compute the score of the best match pub fn score_of(&self, candidate: &str) -> Option { self.find(candidate) .map(|nm| nm.score) } } #[cfg(test)] mod fuzzy_pattern_tests { use { super::*, crate::pattern::Pos, }; /// check position of the match of the pattern in name fn check_pos(pattern: &str, name: &str, pos: &str) { let pat = FuzzyPattern::from(pattern); let match_pos = pat.find(name).unwrap().pos; let target_pos: Pos = pos.chars() .enumerate() .filter(|(_, c)| *c=='^') .map(|(i, _)| i) .collect(); assert_eq!(match_pos, target_pos); } /// test the quality of match length and groups number minimization #[test] fn check_match_pos() { check_pos( "ba", "babababaaa", "^^ ", ); check_pos( "baa", "babababaaa", " ^^^ ", ); check_pos( "bbabaa", "babababaaa", " ^ ^^^^^ ", ); check_pos( "aoml", "bacon.coml", " ^ ^^^", ); check_pos( "broot", "ab br bro oxt ", " ^^^ ^ ^ ", ); check_pos( "broot", "b_broooooot_broot", " ^^^^^", ); check_pos( "buityp", "builder-styles-less-typing.d.ts", "^^^ ^^^ ", ); check_pos( "broot", "ветер br x o vent ootot", " ^^ ^^^ ", ); check_pos( "broot", "brbroorrbbbbbrrooorobrototooooot.txt", " ^^^ ^^ ", ); check_pos( "besh", "benches/shared", "^^ ^^ ", ); } /// check that the scores of all names are strictly decreasing /// (pattern is first tested against itself). /// We verify this property with both computation functions. fn check_ordering_for(pattern: &str, names: &[&str]) { let fp = FuzzyPattern::from(pattern); let mut last_score = fp.find(pattern).map(|m| m.score); let mut last_name = pattern; for name in names { let score = fp.find(name).map(|m| m.score); assert!( score < last_score, "score({name:?}) should be lower than score({last_name:?}) (using find)" ); last_name = name; last_score = score; } } #[test] fn check_orderings() { check_ordering_for( "broot", &[ "a broot", "abbroot", "abcbroot", " abdbroot", "1234broot1", "12345brrrroooottt", "12345brrr roooottt", "brot", ], ); check_ordering_for( "Abc", &[ "abCd", "aBdc", " abdc", " abdbccccc", " a b c", "nothing", ], ); check_ordering_for( "réveil", &[ "Réveillon", "Réveillons", " réveils", "πréveil", "déréveil", "Rêve-t-il ?", " rêves", ], ); } /// check that we don't fail to find strings differing by diacritics /// or unicode normalization. /// /// Note that we can't go past the limits of unicode normalization /// and for example I don't know how to make 'B́' one char (help welcome). /// This should normally not cause any problem for the user as searching /// `"ab"` in `"aB́"` will still match. #[test] fn check_equivalences() { fn check_equivalences_in(arr: &[&str]) { for pattern in arr.iter() { let fp = FuzzyPattern::from(pattern); for name in arr.iter() { println!("looking for pattern {pattern:?} in name {name:?}"); assert!(fp.find(name).unwrap().score > 0); } } } check_equivalences_in(&[ "aB", "ab", "àb", "âB", ]); let c12 = "Comunicações"; assert_eq!(c12.len(), 14); assert_eq!(c12.chars().count(), 12); let c14 = "Comunicações"; assert_eq!(c14.len(), 16); assert_eq!(c14.chars().count(), 14); check_equivalences_in(&[ "comunicacoes", c12, c14, ]); check_equivalences_in(&[ "у", "У", ]); } /// check that there's no problem with ignoring case on cyrillic. /// This problem arises when secular was compiled without the "bmp" feature. /// See https://github.com/Canop/broot/issues/746 #[test] fn issue_746() { let fp = FuzzyPattern::from("устр"); assert!(fp.find("Устройства").is_some()); } } broot-1.46.3/src/pattern/input_pattern.rs000064400000000000000000000055571046102023000165660ustar 00000000000000use { super::*, crate::{ app::AppContext, errors::PatternError, pattern::{ Pattern, PatternParts, }, }, bet::BeTree, lazy_regex::*, }; /// wraps both /// - the "pattern" (which may be used to filter and rank file entries) /// - the source raw string which was used to build it and which may /// be put back in the input. #[derive(Debug, Clone)] pub struct InputPattern { pub raw: String, pub pattern: Pattern, } impl PartialEq for InputPattern { fn eq(&self, other: &Self) -> bool { self.raw == other.raw } } impl InputPattern { pub fn none() -> Self { Self { raw: String::new(), pattern: Pattern::None, } } pub fn new( raw: String, parts_expr: &BeTree, con: &AppContext, ) -> Result { let pattern = Pattern::new(parts_expr, &con.search_modes, con.content_search_max_file_size)?; Ok(Self { raw, pattern }) } pub fn is_none(&self) -> bool { self.pattern.is_empty() } pub fn is_some(&self) -> bool { self.pattern.is_some() } /// empties the pattern and return it /// Similar to Option::take pub fn take(&mut self) -> Self { std::mem::replace(self, Self::none()) } pub fn as_option(self) -> Option { if self.is_some() { Some(self) } else { None } } /// from a pattern used to filter a tree, build a pattern /// which would make sense to filter a previewed file pub fn tree_to_preview(&self) -> Self { let regex_parts: Option<(String, String)> = match &self.pattern { Pattern::ContentExact(cp) => Some(cp.to_regex_parts()), Pattern::ContentRegex(rp) => Some(rp.to_regex_parts()), Pattern::Composite(cp) => cp.expr .iter_atoms() .find_map(|p| match p { Pattern::ContentExact(ce) => Some(ce.to_regex_parts()), Pattern::ContentRegex(cp) => Some(cp.to_regex_parts()), _ => None }), _ => None, }; regex_parts .map(|(core, modifiers)| // The regex part is missing the escaping which prevents it from // ending the pattern in the input. We need to restore it // See https://github.com/Canop/broot/issues/778 (regex_replace_all!("[ :]", &core, "\\$0").to_string(), modifiers) ) .and_then(|(core, modifiers)| RegexPattern::from(&core, &modifiers).ok()) .map(|rp| InputPattern { raw: rp.to_string(), // this adds the initial / pattern: Pattern::NameRegex(rp), }) .unwrap_or_else(InputPattern::none) } } broot-1.46.3/src/pattern/mod.rs000064400000000000000000000025041046102023000144360ustar 00000000000000 mod candidate; mod composite_pattern; mod content_pattern; mod content_regex_pattern; mod exact_pattern; mod fuzzy_pattern; mod input_pattern; mod name_match; mod operator; mod pattern; mod pattern_object; mod pattern_parts; mod pos; mod regex_pattern; mod search_mode; mod tok_pattern; pub use { candidate::Candidate, composite_pattern::CompositePattern, content_pattern::ContentExactPattern, content_regex_pattern::ContentRegexPattern, exact_pattern::ExactPattern, fuzzy_pattern::FuzzyPattern, input_pattern::InputPattern, name_match::NameMatch, pattern::Pattern, pattern_object::PatternObject, pattern_parts::PatternParts, pos::*, operator::PatternOperator, regex_pattern::RegexPattern, search_mode::*, tok_pattern::*, }; use { crate::errors::PatternError, lazy_regex::regex, }; pub fn build_regex(pat: &str, flags: &str) -> Result { let mut builder = regex::RegexBuilder::new(pat); for c in flags.chars() { match c { 'i' => { builder.case_insensitive(true); } 'U' => { builder.swap_greed(true); } _ => { return Err(PatternError::UnknownRegexFlag { bad: c }); } } } Ok(builder.build()?) } broot-1.46.3/src/pattern/name_match.rs000064400000000000000000000031661046102023000157600ustar 00000000000000use { super::Pos, smallvec::SmallVec, }; /// A NameMatch is a positive result of pattern matching inside /// a filename or subpath #[derive(Debug, Clone)] pub struct NameMatch { pub score: i32, // score of the match, guaranteed strictly positive, bigger is better pub pos: Pos, // positions of the matching chars } impl NameMatch { /// wraps any group of matching characters with match_start and match_end pub fn wrap(&self, name: &str, match_start: &str, match_end: &str) -> String { let mut result = String::new(); let mut index_in_pos = 0; let mut wrapped = false; for (idx, c) in name.chars().enumerate() { if index_in_pos < self.pos.len() && self.pos[index_in_pos] == idx { index_in_pos += 1; if !wrapped { result.push_str(match_start); wrapped = true; } } else if wrapped { result.push_str(match_end); wrapped = false; } result.push(c); } if wrapped { result.push_str(match_end); } result } // cut the name match in two parts by recomputing the pos // arrays pub fn cut_after(&mut self, chars_count: usize) -> Self { let mut tail = Self { score: self.score, pos: SmallVec::new(), }; let idx = self.pos.iter().position(|&p| p >= chars_count); if let Some(idx) = idx { for p in self.pos.drain(idx..) { tail.pos.push(p - chars_count); } } tail } } broot-1.46.3/src/pattern/operator.rs000064400000000000000000000002021046102023000155030ustar 00000000000000/// operators combining patterns #[derive(Debug, Clone, Copy, PartialEq)] pub enum PatternOperator { And, Or, Not, } broot-1.46.3/src/pattern/pattern.rs000064400000000000000000000233151046102023000153370ustar 00000000000000use { super::*, crate::{ content_search::ContentMatch, errors::PatternError, }, bet::BeTree, std::{ path::Path, }, }; /// a pattern for filtering and sorting files. #[derive(Debug, Clone)] pub enum Pattern { None, NameExact(ExactPattern), NameFuzzy(FuzzyPattern), NameRegex(RegexPattern), NameTokens(TokPattern), PathExact(ExactPattern), PathFuzzy(FuzzyPattern), PathRegex(RegexPattern), PathTokens(TokPattern), ContentExact(ContentExactPattern), ContentRegex(ContentRegexPattern), Composite(CompositePattern), } impl Pattern { pub fn new( raw_expr: &BeTree, search_modes: &SearchModeMap, content_search_max_file_size: usize, ) -> Result { let expr: BeTree = raw_expr .try_map_atoms::<_, PatternError, _>(|pattern_parts| { let core = pattern_parts.core(); Ok( if core.is_empty() { Pattern::None } else { let parts_mode = pattern_parts.mode(); let mode = search_modes.search_mode(parts_mode)?; let flags = pattern_parts.flags(); match mode { SearchMode::NameExact => Self::NameExact( ExactPattern::from(core) ), SearchMode::NameFuzzy => Self::NameFuzzy( FuzzyPattern::from(core) ), SearchMode::NameRegex => Self::NameRegex( RegexPattern::from(core, flags.unwrap_or(""))? ), SearchMode::NameTokens => Self::NameTokens( TokPattern::new(core) ), SearchMode::PathExact => Self::PathExact( ExactPattern::from(core) ), SearchMode::PathFuzzy => Self::PathFuzzy( FuzzyPattern::from(core) ), SearchMode::PathRegex => Self::PathRegex( RegexPattern::from(core, flags.unwrap_or(""))? ), SearchMode::PathTokens => Self::PathTokens( TokPattern::new(core) ), SearchMode::ContentExact => Self::ContentExact( ContentExactPattern::new(core, content_search_max_file_size) ), SearchMode::ContentRegex => Self::ContentRegex( ContentRegexPattern::new( core, flags.unwrap_or(""), content_search_max_file_size, )? ), } } ) })?; Ok(if expr.is_empty() { Pattern::None } else if expr.is_atomic() { expr.atoms().pop().unwrap() } else { Self::Composite(CompositePattern::new(expr)) }) } pub fn object(&self) -> PatternObject { let mut object = PatternObject::default(); match self { Self::None => {} Self::NameExact(_) | Self::NameFuzzy(_) | Self::NameRegex(_) | Self::NameTokens(_) => { object.name = true; } Self::PathExact(_) | Self::PathFuzzy(_) | Self::PathRegex(_) | Self::PathTokens(_) => { object.subpath = true; } Self::ContentExact(_) | Self::ContentRegex(_) => { object.content = true; } Self::Composite(cp) => { for atom in cp.expr.iter_atoms() { object |= atom.object(); } } } object } pub fn search_string( &self, candidate: &str, ) -> Option { match self { Self::NameExact(ep) | Self::PathExact(ep) => ep.find(candidate), Self::NameFuzzy(fp) | Self::PathFuzzy(fp) => fp.find(candidate), Self::NameRegex(rp) | Self::PathRegex(rp) => rp.find(candidate), Self::NameTokens(tp) | Self::PathTokens(tp) => tp.find(candidate), Self::Composite(cp) => cp.search_string(candidate), _ => None, } } pub fn find_string( &self, candidate: &str, ) -> Option { match self { Self::NameExact(ep) | Self::PathExact(ep) => ep.find(candidate), Self::NameFuzzy(fp) | Self::PathFuzzy(fp) => fp.find(candidate), Self::NameRegex(rp) | Self::PathRegex(rp) => rp.find(candidate), Self::NameTokens(tp) | Self::PathTokens(tp) => tp.find(candidate), Self::Composite(cp) => cp.find_string(candidate), _ => None, } } /// find the content to show next to the name of the file /// when the search involved a content filtering pub fn search_content( &self, candidate: &Path, desired_len: usize, // available space for content match display ) -> Option { match self { Self::ContentExact(cp) => cp.get_content_match(candidate, desired_len), Self::ContentRegex(cp) => cp.get_content_match(candidate, desired_len), Self::Composite(cp) => cp.search_content(candidate, desired_len), _ => None, } } /// find the content to show next to the name of the file /// when the search involved a content filtering and you already /// know the content is there so you don't want to filter by name/path pub fn find_content( &self, candidate: &Path, desired_len: usize, // available space for content match display ) -> Option { match self { Self::ContentExact(cp) => cp.get_content_match(candidate, desired_len), Self::ContentRegex(cp) => cp.get_content_match(candidate, desired_len), Self::Composite(cp) => cp.find_content(candidate, desired_len), _ => None, } } /// get the line of the first match, if any pub fn get_match_line_count( &self, path: &Path, ) -> Option { match self { Self::ContentExact(cp) => cp.get_match_line_count(path), Self::ContentRegex(cp) => cp.get_match_line_count(path), Self::Composite(cp) => cp.get_match_line_count(path), _ => None, } } pub fn score_of(&self, candidate: Candidate) -> Option { match self { Self::NameExact(ep) => ep.score_of(candidate.name), Self::NameFuzzy(fp) => fp.score_of(candidate.name), Self::NameRegex(rp) => rp.find(candidate.name).map(|m| m.score), Self::NameTokens(tp) => tp.score_of(candidate.name), Self::PathExact(ep) => ep.score_of(candidate.subpath), Self::PathFuzzy(fp) => fp.score_of(candidate.subpath), Self::PathRegex(rp) => rp.find(candidate.subpath).map(|m| m.score), Self::PathTokens(tp) => tp.score_of(candidate.subpath), Self::ContentExact(cp) => cp.score_of(candidate), Self::ContentRegex(cp) => cp.score_of(candidate), Self::Composite(cp) => cp.score_of(candidate), Self::None => Some(1), } } pub fn score_of_string(&self, candidate: &str) -> Option { match self { Self::NameExact(ep) => ep.score_of(candidate), Self::NameFuzzy(fp) => fp.score_of(candidate), Self::NameRegex(rp) => rp.find(candidate).map(|m| m.score), Self::NameTokens(tp) => tp.score_of(candidate), Self::PathExact(ep) => ep.score_of(candidate), Self::PathFuzzy(fp) => fp.score_of(candidate), Self::PathRegex(rp) => rp.find(candidate).map(|m| m.score), Self::PathTokens(tp) => tp.score_of(candidate), Self::ContentExact(_) => None, // this isn't suitable Self::ContentRegex(_) => None, // this isn't suitable Self::Composite(cp) => cp.score_of_string(candidate), Self::None => Some(1), } } pub fn is_some(&self) -> bool { !self.is_empty() } /// an empty pattern is one which doesn't discriminate /// (it accepts everything) pub fn is_empty(&self) -> bool { match self { Self::NameExact(ep) | Self::PathExact(ep) => ep.is_empty(), Self::ContentExact(ep) => ep.is_empty(), Self::NameFuzzy(fp) | Self::PathFuzzy(fp) => fp.is_empty(), Self::NameRegex(rp) | Self::PathRegex(rp) => rp.is_empty(), Self::ContentRegex(rp) => rp.is_empty(), Self::NameTokens(tp) | Self::PathTokens(tp) => tp.is_empty(), Self::Composite(cp) => cp.is_empty(), Self::None => true, } } /// whether the scores are more than just 0 or 1. /// When it's the case, the tree builder will look for more matching results /// in order to select the best ones. pub fn has_real_scores(&self) -> bool { match self { Self::NameExact(_) | Self::NameFuzzy(_) => true, Self::PathExact(_) | Self::PathFuzzy(_) => true, Self::Composite(cp) => cp.has_real_scores(), _ => false, } } } broot-1.46.3/src/pattern/pattern_object.rs000064400000000000000000000013511046102023000166610ustar 00000000000000use { std::ops, }; /// on what the search applies /// (a composite pattern may apply to several topic /// hence the bools) #[derive(Debug, Clone, Copy, PartialEq, Default)] pub struct PatternObject { pub name: bool, pub subpath: bool, pub content: bool, } impl ops::BitOr for PatternObject { type Output = Self; fn bitor(self, o: Self) -> Self::Output { Self { name: self.name | o.name, subpath: self.subpath | o.subpath, content: self.content | o.content, } } } impl ops::BitOrAssign for PatternObject { fn bitor_assign(&mut self, rhs: Self) { self.name |= rhs.name; self.subpath |= rhs.subpath; self.content |= rhs.content; } } broot-1.46.3/src/pattern/pattern_parts.rs000064400000000000000000000037211046102023000165470ustar 00000000000000use { std::fmt, }; /// An intermediate parsed representation of the raw string making /// a pattern, with up to 3 parts (search mode, core pattern, modifiers) #[derive(Debug, Clone, PartialEq)] pub struct PatternParts { /// can't be empty by construct parts: Vec, } impl fmt::Display for PatternParts { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self.parts.len() { 1 => write!(f, "{}", &self.parts[0]), 2 => write!(f, "{}/{}", &self.parts[0], &self.parts[1]), _ => write!(f, "{}/{}/{}", &self.parts[0], &self.parts[1], &self.parts[2]), } } } impl Default for PatternParts { fn default() -> Self { Self { parts: vec![String::new()], } } } #[cfg(test)] impl TryFrom<&[&str]> for PatternParts { type Error = &'static str; fn try_from(a: &[&str]) -> Result { if a.is_empty() { return Err("invalid empty parts array"); } let parts = a.iter().map(|s| (*s).into()).collect(); Ok(Self { parts }) } } impl PatternParts { pub fn push(&mut self, c: char) { // self.parts can't be empty, by construct self.parts.last_mut().unwrap().push(c); } pub fn is_between_slashes(&self) -> bool { self.parts.len() == 2 } pub fn add_part(&mut self) { self.parts.push(String::new()); } pub fn is_empty(&self) -> bool { self.core().is_empty() } pub fn core(&self) -> &str { if self.parts.len() > 1 { &self.parts[1] } else { &self.parts[0] } } pub fn mode(&self) -> Option<&String> { if self.parts.len() > 1 { self.parts.first() } else { None } } pub fn flags(&self) -> Option<&str> { if self.parts.len() > 2 { self.parts.get(2).map(|s| s.as_str()) } else { None } } } broot-1.46.3/src/pattern/pos.rs000064400000000000000000000002101046102023000144500ustar 00000000000000use { smallvec::SmallVec, }; /// a vector of indexes of the matching characters (not bytes) pub type Pos = SmallVec<[usize; 8]>; broot-1.46.3/src/pattern/regex_pattern.rs000064400000000000000000000026761046102023000165400ustar 00000000000000//! a filtering pattern using a regular expression use { super::NameMatch, crate::errors::PatternError, lazy_regex::regex, smallvec::SmallVec, std::fmt, }; #[derive(Debug, Clone)] pub struct RegexPattern { rex: regex::Regex, flags: String, } impl fmt::Display for RegexPattern { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if self.flags.is_empty() { write!(f, "/{}", self.rex) } else { write!(f, "/{}/{}", self.rex, self.flags) } } } impl RegexPattern { pub fn from(pat: &str, flags: &str) -> Result { Ok(RegexPattern { rex: super::build_regex(pat, flags)?, flags: flags.to_string(), }) } /// return a match if the pattern can be found in the candidate string pub fn find(&self, candidate: &str) -> Option { // note that there's no significative cost related to using // find over is_match self.rex.find(candidate).map(|rm| { let chars_before = candidate[..rm.start()].chars().count(); let rm_chars = rm.as_str().chars().count(); let mut pos = SmallVec::with_capacity(rm_chars); for i in 0..rm_chars { pos.push(chars_before + i); } super::NameMatch { score: 1, pos } }) } pub fn is_empty(&self) -> bool { self.rex.as_str().is_empty() } } broot-1.46.3/src/pattern/search_mode.rs000064400000000000000000000217211046102023000161320ustar 00000000000000 use { crate::{ app::AppContext, errors::{ConfError, PatternError}, }, rustc_hash::FxHashMap, lazy_regex::regex_is_match, std::convert::TryFrom, }; /// where to search #[derive(Debug, Clone, Copy, PartialEq)] pub enum SearchObject { Name, Path, Content, } /// how to search #[derive(Debug, Clone, Copy, PartialEq)] pub enum SearchKind { Exact, Fuzzy, Regex, Tokens, } /// a valid combination of SearchObject and SearchKind, /// determine how a pattern will be used #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum SearchMode { NameExact, NameFuzzy, NameRegex, NameTokens, PathExact, PathFuzzy, PathRegex, PathTokens, ContentExact, ContentRegex, } pub static SEARCH_MODES: &[SearchMode] = &[ SearchMode::NameFuzzy, SearchMode::NameRegex, SearchMode::NameExact, SearchMode::NameTokens, SearchMode::PathExact, SearchMode::PathFuzzy, SearchMode::PathRegex, SearchMode::PathTokens, SearchMode::ContentExact, SearchMode::ContentRegex, ]; impl SearchMode { fn new(search_object: SearchObject, search_kind: SearchKind) -> Option { use { SearchObject::*, SearchKind::*, }; match (search_object, search_kind) { (Name, Exact) => Some(Self::NameExact), (Name, Fuzzy) => Some(Self::NameFuzzy), (Name, Regex) => Some(Self::NameRegex), (Name, Tokens) => Some(Self::NameTokens), (Path, Exact) => Some(Self::PathExact), (Path, Fuzzy) => Some(Self::PathFuzzy), (Path, Regex) => Some(Self::PathRegex), (Path, Tokens) => Some(Self::PathTokens), (Content, Exact) => Some(Self::ContentExact), (Content, Fuzzy) => None, // unsupported for now - could be but why ? (Content, Regex) => Some(Self::ContentRegex), (Content, Tokens) => None, // unsupported for now - could be but need bench } } /// Return the prefix to type, eg "/" in standard for a name-regex, /// "" for a name-fuzzy, and "ep" for a path-exact pub fn prefix(self, con: &AppContext) -> String { con .search_modes .key(self) .map_or_else(|| "".to_string(), |k| format!("{k}/")) } pub fn object(self) -> SearchObject { match self { Self::NameExact | Self::NameFuzzy | Self::NameRegex | Self::NameTokens => SearchObject::Name, Self::PathExact | Self::PathFuzzy | Self::PathRegex | Self::PathTokens => SearchObject::Path, Self::ContentExact | Self::ContentRegex => SearchObject::Content, } } pub fn kind(self) -> SearchKind { match self { Self::NameExact => SearchKind::Exact, Self::NameFuzzy => SearchKind::Fuzzy, Self::NameRegex => SearchKind::Regex, Self::NameTokens => SearchKind::Tokens, Self::PathExact => SearchKind::Exact, Self::PathFuzzy => SearchKind::Fuzzy, Self::PathRegex => SearchKind::Regex, Self::PathTokens => SearchKind::Tokens, Self::ContentExact => SearchKind::Exact, Self::ContentRegex => SearchKind::Regex, } } } /// define a mapping from a search mode which can be typed in /// the input to a SearchMode value #[derive(Debug, Clone)] pub struct SearchModeMapEntry { pub key: Option, pub mode: SearchMode, } /// manage how to find the search mode to apply to a /// pattern taking the config in account. #[derive(Debug, Clone)] pub struct SearchModeMap { pub entries: Vec, } impl SearchModeMapEntry { pub fn parse(conf_key: &str, conf_mode: &str) -> Result { let mut search_kinds = Vec::new(); let mut search_objects = Vec::new(); let s = conf_mode.to_lowercase(); for t in s.split_whitespace() { match t { "exact" => search_kinds.push(SearchKind::Exact), "fuzzy" => search_kinds.push(SearchKind::Fuzzy), "regex" => search_kinds.push(SearchKind::Regex), "tokens" => search_kinds.push(SearchKind::Tokens), "name" => search_objects.push(SearchObject::Name), "content" => search_objects.push(SearchObject::Content), "path" => search_objects.push(SearchObject::Path), _ => { return Err(ConfError::InvalidSearchMode { details: format!("{t:?} not understood in search mode definition"), }); } } } if search_kinds.is_empty() { return Err(ConfError::InvalidSearchMode { details: "missing search kind in search mode definition\ (the search kind must be one of 'exact', 'fuzzy', 'regex', 'tokens')".to_string() }); } if search_kinds.len() > 1 { return Err(ConfError::InvalidSearchMode { details: "only one search kind can be specified in a search mode".to_string() }); } if search_objects.is_empty() { return Err(ConfError::InvalidSearchMode { details: "missing search object in search mode definition\ (the search object must be one of 'name', 'path', 'content')".to_string() }); } if search_objects.len() > 1 { return Err(ConfError::InvalidSearchMode { details: "only one search object can be specified in a search mode".to_string() }); } let mode = match SearchMode::new(search_objects[0], search_kinds[0]) { Some(mode) => mode, None => { return Err(ConfError::InvalidSearchMode { details: "Unsupported combination of search object and kind".to_string() }); }, }; let key = if conf_key.is_empty() || conf_key == "" { // serde toml parser doesn't handle correctly empty keys so we accept as // alternative the `"" = "fuzzy name"` solution. // TODO look at issues and/or code in serde-toml None } else if regex_is_match!(r"^\w*/$", conf_key) { Some(conf_key[0..conf_key.len() - 1].to_string()) } else { return Err(ConfError::InvalidKey { raw: conf_key.to_string(), }); }; Ok(SearchModeMapEntry { key, mode }) } } impl Default for SearchModeMap { fn default() -> Self { let mut smm = SearchModeMap { entries: Vec::new(), }; // the last keys are preferred smm.setm(&["ne", "en", "e"], SearchMode::NameExact); smm.setm(&["nf", "fn", "n", "f"], SearchMode::NameFuzzy); smm.setm(&["r", "nr", "rn", ""], SearchMode::NameRegex); smm.setm(&["pe", "ep"], SearchMode::PathExact); smm.setm(&["pf", "fp", "p"], SearchMode::PathFuzzy); smm.setm(&["pr", "rp"], SearchMode::PathRegex); smm.setm(&["ce", "ec", "c"], SearchMode::ContentExact); smm.setm(&["rx", "cr"], SearchMode::ContentRegex); smm.setm(&["pt", "tp", "t"], SearchMode::PathTokens); smm.setm(&["tn", "nt"], SearchMode::NameTokens); smm.set(SearchModeMapEntry { key: None, mode: SearchMode::PathFuzzy }); smm } } impl TryFrom<&FxHashMap> for SearchModeMap { type Error = ConfError; fn try_from(map: &FxHashMap) -> Result { let mut smm = Self::default(); for (k, v) in map { smm.entries.push(SearchModeMapEntry::parse(k, v)?); } Ok(smm) } } impl SearchModeMap { pub fn setm(&mut self, keys: &[&str], mode: SearchMode) { for key in keys { self.set(SearchModeMapEntry { key: Some(key.to_string()), mode, }); } } /// we don't remove existing entries to ensure there's always a matching entry in /// mode->key (but search iterations will be done in reverse) pub fn set(&mut self, entry: SearchModeMapEntry) { self.entries.push(entry); } pub fn search_mode(&self, key: Option<&String>) -> Result { for entry in self.entries.iter().rev() { if entry.key.as_ref() == key { return Ok(entry.mode); } } Err(PatternError::InvalidMode { mode: if let Some(key) = key { format!("{key}/") } else { "".to_string() }, }) } pub fn key(&self, search_mode: SearchMode) -> Option<&String> { for entry in self.entries.iter().rev() { if entry.mode == search_mode { return entry.key.as_ref(); } } warn!("search mode key not found for {:?}", search_mode); // should not happen None } } broot-1.46.3/src/pattern/tok_pattern.rs000064400000000000000000000172151046102023000162160ustar 00000000000000use { super::NameMatch, secular, smallvec::{smallvec, SmallVec}, std::{ cmp::Reverse, ops::Range, }, }; type CandChars = SmallVec<[char; 32]>; static SEPARATORS: &[char] = &[',', ';']; // weights used in match score computing const BONUS_MATCH: i32 = 50_000; const BONUS_CANDIDATE_LENGTH: i32 = -1; // per char pub fn norm_chars(s: &str) -> Box<[char]> { secular::normalized_lower_lay_string(s) .chars() .collect::>() .into_boxed_slice() } /// a list of tokens we want to find, non overlapping /// and in any order, in strings #[derive(Debug, Clone, PartialEq)] pub struct TokPattern { toks: Vec>, sum_len: usize, } // scoring basis ? // - number of parts of the candidates (separated by / for example) // that are touched by a tok ? // - malus for adjacent ranges // - bonus for ranges starting just after a separator // - bonus for order ? impl TokPattern { pub fn new(pattern: &str) -> Self { // we accept several separators. The first one // we encounter among the possible ones is the // separator of the whole. This allows using the // other char: In ";ab,er", the comma isn't seen // as a separator but as part of a tok let sep = pattern.chars().find(|c| SEPARATORS.contains(c)); let mut toks: Vec> = if let Some(sep) = sep { pattern.split(sep) .filter(|s| !s.is_empty()) .map(norm_chars) .collect() } else if pattern.is_empty() { Vec::new() } else { vec![norm_chars(pattern)] }; // we sort the tokens from biggest to smallest // because the current algorithm stops at the // first match for any tok. Thus it would fail // to find "abc,b" in "abcdb" if it looked first // at the "b" token toks.sort_by_key(|t| Reverse(t.len())); let sum_len = toks.iter().map(|s| s.len()).sum(); Self { toks, sum_len, } } /// an "empty" pattern is one which accepts everything because /// it has no discriminant pub fn is_empty(&self) -> bool { self.sum_len == 0 } /// return either None (no match) or a vec whose size is the number /// of tokens fn find_ranges(&self, candidate: &str) -> Option>> { let mut cand_chars: CandChars = SmallVec::with_capacity(candidate.len()); cand_chars.extend(candidate.chars().map(secular::lower_lay_char)); if cand_chars.len() < self.sum_len || self.sum_len == 0 { return None; } // we first look for the first tok, it's simpler let first_tok = &self.toks[0]; let l = first_tok.len(); let first_matching_range = (0..cand_chars.len()+1-l) .map(|idx| idx..idx+l) .find(|r| { &cand_chars[r.start..r.end] == first_tok.as_ref() }); // we initialize the vec only when the first tok is found first_matching_range .and_then(|first_matching_range| { let mut matching_ranges = vec![first_matching_range]; for tok in self.toks.iter().skip(1) { let l = tok.len(); let matching_range = (0..cand_chars.len()+1-l) .map(|idx| idx..idx+l) .filter(|r| { &cand_chars[r.start..r.end] == tok.as_ref() }) .find(|r| { // check we're not intersecting a previous range for pr in &matching_ranges { if pr.contains(&r.start) || pr.contains(&(r.end-1)) { return false; } } true }); if let Some(r) = matching_range { matching_ranges.push(r); } else { return None; } } Some(matching_ranges) }) } fn score_of_matching(&self, candidate: &str) -> i32 { BONUS_MATCH + BONUS_CANDIDATE_LENGTH * candidate.len() as i32 } /// note that it should not be called on empty patterns pub fn find(&self, candidate: &str) -> Option { self.find_ranges(candidate) .map(|matching_ranges| { let mut pos = smallvec![0; self.sum_len]; let mut i = 0; for r in matching_ranges { for p in r { pos[i] = p; i += 1; } } pos.sort_unstable(); let score = self.score_of_matching(candidate); NameMatch { score, pos } }) } /// compute the score of the best match /// Note that it should not be called on empty patterns pub fn score_of(&self, candidate: &str) -> Option { self.find_ranges(candidate) .map(|_| self.score_of_matching(candidate)) } } #[cfg(test)] mod tok_pattern_tests { use { super::*, crate::pattern::Pos, }; /// check position of the match of the pattern in name fn check_pos(pattern: &str, name: &str, pos: &str) { println!("checking pattern={pattern:?} name={name:?}"); let pat = TokPattern::new(pattern); let match_pos = pat.find(name).unwrap().pos; let target_pos: Pos = pos.chars() .enumerate() .filter(|(_, c)| *c=='^') .map(|(i, _)| i) .collect(); assert_eq!(match_pos, target_pos); } #[test] fn check_match_pos() { check_pos( "m,", "miaou", "^ ", ); check_pos( "bat", "cabat", " ^^^", ); check_pos( ";ba", "babababaaa", "^^ ", ); check_pos( "ba,ca", "bababacaa", "^^ ^^ ", ); check_pos( "sub,doc,2", "/home/user/path2/subpath/Documents/", " ^ ^^^ ^^^", ); check_pos( "ab,abc", "0123/abc/ab/cdg", " ^^^ ^^ ", ); } fn check_match(pattern: &str, name: &str, do_match: bool) { assert_eq!( TokPattern::new(pattern).find(name).is_some(), do_match, ); } #[test] fn test_separators() { let a = TokPattern::new("ab;cd;ef"); let b = TokPattern::new("ab,cd,ef"); assert_eq!(a, b); let a = TokPattern::new(",ab;cd;ef"); assert_eq!(a.toks.len(), 1); assert_eq!(a.toks[0].len(), 8); let a = TokPattern::new(";ab,cd,ef;"); assert_eq!(a.toks.len(), 1); assert_eq!(a.toks[0].len(), 8); } #[test] fn test_match() { check_match("mia", "android/phonegap", false); check_match("mi", "a", false); check_match("mi", "π", false); check_match("mi", "miaou/a", true); check_match("imm", "😍", false); } #[test] fn test_tok_repetitions() { check_match("sub", "rasub", true); check_match("sub,sub", "rasub", false); check_match("sub,sub", "rasubandsub", true); check_match("sub,sub,sub", "rasubandsub", false); check_match("ghi,abc,def,ccc", "abccc/Defghi", false); check_match("ghi,abc,def,ccc", "abcccc/Defghi", true); } } broot-1.46.3/src/permissions/mod.rs000064400000000000000000000004611046102023000153340ustar 00000000000000//////////////////// UNIX #[cfg(not(any(target_family = "windows", target_os = "android")))] pub mod permissions_unix; #[cfg(not(any(target_family = "windows", target_os = "android")))] pub use permissions_unix::*; //////////////////// WINDOWS #[cfg(windows)] pub fn supported() -> bool { false } broot-1.46.3/src/permissions/permissions_unix.rs000064400000000000000000000022121046102023000201670ustar 00000000000000use { rustc_hash::FxHashMap, once_cell::sync::Lazy, std::sync::Mutex, }; pub fn supported() -> bool { true } pub fn user_name(uid: u32) -> String { static USERS_CACHE_MUTEX: Lazy>> = Lazy::new(|| { Mutex::new(FxHashMap::default()) }); let mut users_cache = USERS_CACHE_MUTEX.lock().unwrap(); let name = users_cache .entry(uid) .or_insert_with(|| { uzers::get_user_by_uid(uid).map_or_else( || "????".to_string(), |u| u.name().to_string_lossy().to_string(), ) }); (*name).to_string() } pub fn group_name(gid: u32) -> String { static GROUPS_CACHE_MUTEX: Lazy>> = Lazy::new(|| { Mutex::new(FxHashMap::default()) }); let mut groups_cache = GROUPS_CACHE_MUTEX.lock().unwrap(); let name = groups_cache .entry(gid) .or_insert_with(|| { uzers::get_group_by_gid(gid).map_or_else( || "????".to_string(), |u| u.name().to_string_lossy().to_string(), ) }); (*name).to_string() } broot-1.46.3/src/preview/dir_view.rs000064400000000000000000000065741046102023000155060ustar 00000000000000use { crate::{ app::{AppContext, DisplayContext}, command::ScrollCommand, display::{DisplayableTree, Screen, W}, errors::ProgramError, pattern::InputPattern, skin::PanelSkin, task_sync::Dam, tree_build::{TreeBuilder}, tree::{Tree, TreeOptions}, }, crokey::crossterm::{ cursor, QueueableCommand, }, std::{ io, path::PathBuf, }, termimad::Area, }; pub struct DirView { pub tree: Tree, page_height: Option, } impl DirView { pub fn new( dir: PathBuf, pattern: InputPattern, dam: &Dam, con: &AppContext, ) -> Result { let options = TreeOptions { show_hidden: true, respect_git_ignore: false, pattern, ..Default::default() }; let mut builder = TreeBuilder::from( dir, options, 100, con, ).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; builder.deep = false; let tree = builder .build_tree( false, // on refresh we always do a non total search dam, ) .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; Ok(Self { tree, page_height: None, }) } pub fn display( &mut self, w: &mut W, disc: &DisplayContext, area: &Area, ) -> Result<(), ProgramError> { let page_height = area.height as usize; if Some(page_height) != self.page_height { self.page_height = Some(page_height); } let dp = DisplayableTree { app_state: None, tree: &self.tree, skin: &disc.panel_skin.styles, ext_colors: &disc.con.ext_colors, area: area.clone(), in_app: true, }; dp.write_on(w)?; Ok(()) } pub fn display_info( &mut self, w: &mut W, _screen: Screen, panel_skin: &PanelSkin, area: &Area, ) -> Result<(), ProgramError> { let width = area.width as usize; let mut s = format!("{}", self.tree.lines.len()); if s.len() > width { return Ok(()); } if s.len() + "lines: ".len() < width { s = format!("entries: {s}"); } w.queue(cursor::MoveTo( area.left + area.width - s.len() as u16, area.top, ))?; panel_skin.styles.default.queue(w, s)?; Ok(()) } pub fn try_scroll( &mut self, cmd: ScrollCommand, ) -> bool { let Some(page_height) = self.page_height else { return false; }; let dy = cmd.to_lines(page_height); self.tree.try_scroll(dy, page_height) } pub fn try_select_y(&mut self, y: u16) -> bool { self.tree.try_select_y(y as usize) } pub fn move_selection(&mut self, dy: i32, cycle: bool) { if let Some(page_height) = self.page_height { self.tree.move_selection(dy, page_height, cycle); } } pub fn select_first(&mut self) { self.tree.try_select_first(); } pub fn select_last(&mut self) { if let Some(page_height) = self.page_height { self.tree.try_select_last(page_height); } } } broot-1.46.3/src/preview/mod.rs000064400000000000000000000011101046102023000144320ustar 00000000000000mod dir_view; mod preview; mod preview_transformer; mod preview_state; mod zero_len_file_view; pub use { dir_view::DirView, preview::Preview, preview_transformer::*, preview_state::PreviewState, zero_len_file_view::ZeroLenFileView, }; #[derive(Debug, Clone, Copy, PartialEq, serde::Deserialize)] #[serde(rename_all = "snake_case")] pub enum PreviewMode { /// image Image, /// show the content as text, with syntax coloring if /// it makes sense. Fails if the file isn't in UTF8 Text, /// show the content of the file as hex Hex, } broot-1.46.3/src/preview/preview.rs000064400000000000000000000273331046102023000153530ustar 00000000000000use { super::*, crate::{ app::*, command::ScrollCommand, display::*, errors::ProgramError, hex::HexView, image::ImageView, pattern::InputPattern, skin::PanelSkin, syntactic::SyntacticView, task_sync::Dam, }, crokey::crossterm::{cursor, QueueableCommand}, std::{ io, path::Path, }, termimad::{Area, CropWriter, SPACE_FILLING}, }; pub enum Preview { Dir(DirView), Image(ImageView), Syntactic(SyntacticView), Hex(HexView), ZeroLen(ZeroLenFileView), IoError(io::Error), } impl Preview { /// build a preview, never failing (but the preview can be Preview::IOError). /// If the preferred mode can't be applied, an other mode is chosen. pub fn new( path: &Path, preferred_mode: Option, con: &AppContext, ) -> Self { if path.is_file() { match preferred_mode { Some(PreviewMode::Hex) => Self::hex(path), Some(PreviewMode::Image) => Self::image(path), Some(PreviewMode::Text) => Self::unfiltered_text(path, con), None => { // automatic behavior: image, text, hex ImageView::new(path) .map(Self::Image) .unwrap_or_else(|_| Self::unfiltered_text(path, con)) } } } else { Self::dir(path, InputPattern::none(), &Dam::unlimited(), con) } } /// try to build a preview with the designed mode, return an error /// if that wasn't possible pub fn with_mode( path: &Path, mode: PreviewMode, con: &AppContext, ) -> Result { if path.is_file() { match mode { PreviewMode::Hex => { Ok(HexView::new(path.to_path_buf()).map(Self::Hex)?) } PreviewMode::Image => { ImageView::new(path).map(Self::Image) } PreviewMode::Text => { Ok( SyntacticView::new(path, InputPattern::none(), &mut Dam::unlimited(), con, false) .transpose() .expect("syntactic view without pattern shouldn't be none") .map(Self::Syntactic)?, ) } } } else { Ok(Self::dir(path, InputPattern::none(), &Dam::unlimited(), con)) } } /// build a dir preview pub fn dir(path: &Path, pattern: InputPattern, dam: &Dam, con: &AppContext) -> Self { match DirView::new(path.to_path_buf(), pattern, dam, con) { Ok(dv) => Self::Dir(dv), Err(e) => Self::IoError(e), } } /// build an image view, unless the file can't be interpreted /// as an image, in which case a hex view is used pub fn image(path: &Path) -> Self { ImageView::new(path) .ok() .map(Self::Image) .unwrap_or_else(|| Self::hex(path)) } /// build a text preview (maybe with syntaxic coloring) if possible, /// a hex (binary) view if content isnt't UTF8, a ZeroLen file if there's /// no length (it's probably a linux pseudofile) or a IOError when /// there's a IO problem pub fn unfiltered_text( path: &Path, con: &AppContext, ) -> Self { match SyntacticView::new(path, InputPattern::none(), &mut Dam::unlimited(), con, false) { Ok(Some(sv)) => Self::Syntactic(sv), Err(ProgramError::ZeroLenFile | ProgramError::UnmappableFile) => { debug!("zero len or unmappable file - check if system file"); Self::ZeroLen(ZeroLenFileView::new(path.to_path_buf())) } Err(ProgramError::SyntectCrashed { details }) => { warn!("syntect crashed with message : {details:?}"); Self::unstyled_text(path, con) } // not previewable as UTF8 text // we'll try reading it as binary Err(ProgramError::UnprintableFile) => Self::hex(path), _ => Self::hex(path), } } /// build a text preview with no syntax highlighting, if possible pub fn unstyled_text( path: &Path, con: &AppContext, ) -> Self { match SyntacticView::new(path, InputPattern::none(), &mut Dam::unlimited(), con, true) { Ok(Some(sv)) => Self::Syntactic(sv), Err(ProgramError::ZeroLenFile | ProgramError::UnmappableFile) => { debug!("zero len or unmappable file - check if system file"); Self::ZeroLen(ZeroLenFileView::new(path.to_path_buf())) } // not previewable as UTF8 text - we'll try reading it as binary Err(ProgramError::UnprintableFile) => Self::hex(path), _ => Self::hex(path), } } /// try to build a filtered view. Will return None if /// the dam gets an event before it's built pub fn filtered( &self, path: &Path, pattern: InputPattern, dam: &mut Dam, con: &AppContext, ) -> Option { if path.is_file() { match self { Self::Syntactic(_) => { match SyntacticView::new(path, pattern, dam, con, false) { // normal finished loading Ok(Some(sv)) => Some(Self::Syntactic(sv)), // interrupted search Ok(None) => None, // not previewable as UTF8 text // we'll try reading it as binary Err(_) => Some(Self::hex(path)), // FIXME try as unstyled if syntect crashed } } _ => None, // not filterable } } else { Some(Self::dir(path, pattern, dam, con)) } } /// return a hex_view, suitable for binary, or Self::IOError /// if there was an error pub fn hex(path: &Path) -> Self { match HexView::new(path.to_path_buf()) { Ok(reader) => Self::Hex(reader), Err(e) => { // it's unlikely as the file isn't open at this point warn!("error while previewing {:?} : {:?}", path, e); Self::IoError(e) } } } /// return the preview_mode, or None if we're on IOError or Directory pub fn get_mode(&self) -> Option { match self { Self::Image(_) => Some(PreviewMode::Image), Self::Syntactic(_) => Some(PreviewMode::Text), Self::ZeroLen(_) => Some(PreviewMode::Text), Self::Hex(_) => Some(PreviewMode::Hex), Self::IoError(_) => None, Self::Dir(_) => None, } } pub fn pattern(&self) -> InputPattern { match self { Self::Dir(dv) => dv.tree.options.pattern.clone(), Self::Syntactic(sv) => sv.pattern.clone(), _ => InputPattern::none(), } } pub fn try_scroll( &mut self, cmd: ScrollCommand, ) -> bool { match self { Self::Dir(dv) => dv.try_scroll(cmd), Self::Syntactic(sv) => sv.try_scroll(cmd), Self::Hex(hv) => hv.try_scroll(cmd), _ => false, } } pub fn is_filterable(&self) -> bool { matches!(self, Self::Syntactic(_) | Self::Dir(_)) } pub fn get_selected_line(&self) -> Option { match self { Self::Syntactic(sv) => sv.get_selected_line(), _ => None, } } pub fn get_selected_line_number(&self) -> Option { match self { Self::Syntactic(sv) => sv.get_selected_line_number(), _ => None, } } pub fn try_select_line_number(&mut self, number: usize) -> bool { match self { Self::Syntactic(sv) => sv.try_select_line_number(number), _ => false, } } pub fn unselect(&mut self) { // it's not possible to unselect in a dir_view if let Self::Syntactic(sv) = self { sv.unselect(); } } pub fn try_select_y(&mut self, y: u16) -> bool { match self { Self::Dir(dv) => dv.try_select_y(y), Self::Syntactic(sv) => sv.try_select_y(y), _ => false, } } pub fn move_selection(&mut self, dy: i32, cycle: bool) { match self { Self::Dir(dv) => dv.move_selection(dy, cycle), Self::Syntactic(sv) => sv.move_selection(dy, cycle), Self::Hex(hv) => { hv.try_scroll(ScrollCommand::Lines(dy)); } _ => {} } } pub fn previous_match(&mut self) { if let Self::Syntactic(sv) = self { sv.previous_match(); } else { self.move_selection(-1, true); } } pub fn next_match(&mut self) { if let Self::Syntactic(sv) = self { sv.next_match(); } else { self.move_selection(1, true); } } pub fn select_first(&mut self) { match self { Self::Dir(dv) => dv.select_first(), Self::Syntactic(sv) => sv.select_first(), Self::Hex(hv) => hv.select_first(), _ => {} } } pub fn select_last(&mut self) { match self { Self::Syntactic(sv) => sv.select_last(), Self::Hex(hv) => hv.select_last(), _ => {} } } pub fn display( &mut self, w: &mut W, disc: &DisplayContext, area: &Area, ) -> Result<(), ProgramError> { let panel_skin = &disc.panel_skin; let screen = disc.screen; let con = &disc.con; match self { Self::Dir(dv) => dv.display(w, disc, area), Self::Image(iv) => iv.display(w, disc, area), Self::Syntactic(sv) => sv.display(w, screen, panel_skin, area, con), Self::ZeroLen(zlv) => zlv.display(w, screen, panel_skin, area), Self::Hex(hv) => hv.display(w, screen, panel_skin, area), Self::IoError(err) => { let mut y = area.top; w.queue(cursor::MoveTo(area.left, y))?; let mut cw = CropWriter::new(w, area.width as usize); cw.queue_str(&panel_skin.styles.default, "An error prevents the preview:")?; cw.fill(&panel_skin.styles.default, &SPACE_FILLING)?; y += 1; w.queue(cursor::MoveTo(area.left, y))?; let mut cw = CropWriter::new(w, area.width as usize); cw.queue_g_string(&panel_skin.styles.status_error, err.to_string())?; cw.fill(&panel_skin.styles.default, &SPACE_FILLING)?; y += 1; while y < area.top + area.height { w.queue(cursor::MoveTo(area.left, y))?; let mut cw = CropWriter::new(w, area.width as usize); cw.fill(&panel_skin.styles.default, &SPACE_FILLING)?; y += 1; } Ok(()) } } } pub fn display_info( &mut self, w: &mut W, screen: Screen, panel_skin: &PanelSkin, area: &Area, ) -> Result<(), ProgramError> { match self { Self::Dir(dv) => dv.display_info(w, screen, panel_skin, area), Self::Image(iv) => iv.display_info(w, screen, panel_skin, area), Self::Syntactic(sv) => sv.display_info(w, screen, panel_skin, area), Self::Hex(hv) => hv.display_info(w, screen, panel_skin, area), _ => Ok(()), } } } broot-1.46.3/src/preview/preview_state.rs000064400000000000000000000363201046102023000165470ustar 00000000000000use { super::*, crate::{ app::*, command::{Command, ScrollCommand, TriggerType}, display::{Screen, W}, errors::ProgramError, flag::Flag, pattern::InputPattern, task_sync::Dam, tree::TreeOptions, verb::*, }, crokey::crossterm::{ cursor, QueueableCommand, }, std::path::{Path, PathBuf}, termimad::{Area, CropWriter, SPACE_FILLING}, }; /// an application state dedicated to previewing files. /// /// It's usually the only state in its panel and is kept when the /// selection changes (other panels indirectly call `set_selected_path`). pub struct PreviewState { pub preview_area: Area, dirty: bool, // true when background must be cleared source_path: PathBuf, // path to the file whose preview is requested transform: Option, preview: Preview, pending_pattern: InputPattern, // a pattern (or not) which has not yet be applied filtered_preview: Option, removed_pattern: InputPattern, preferred_mode: Option, tree_options: TreeOptions, mode: Mode, } impl PreviewState { pub fn new( source_path: PathBuf, pending_pattern: InputPattern, preferred_mode: Option, tree_options: TreeOptions, con: &AppContext, ) -> PreviewState { let preview_area = Area::uninitialized(); // will be fixed at drawing time let transform = con.preview_transformers.transform(&source_path, preferred_mode); let preview_path = transform.as_ref().map(|c| &c.output_path).unwrap_or(&source_path); let preview = Preview::new(preview_path, preferred_mode, con); PreviewState { preview_area, dirty: true, source_path, transform, preview, pending_pattern, filtered_preview: None, removed_pattern: InputPattern::none(), preferred_mode, tree_options, mode: con.initial_mode(), } } pub fn preview_path(&self) -> &Path { self.transform.as_ref().map(|c| &c.output_path).unwrap_or(&self.source_path) } fn vis_preview(&self) -> &Preview { self.filtered_preview.as_ref().unwrap_or(&self.preview) } fn mut_preview(&mut self) -> &mut Preview { self.filtered_preview.as_mut().unwrap_or(&mut self.preview) } fn set_mode( &mut self, mode: PreviewMode, con: &AppContext, ) -> Result { if self.preview.get_mode() == Some(mode) { return Ok(CmdResult::Keep); } Ok(match Preview::with_mode(self.preview_path(), mode, con) { Ok(preview) => { self.preview = preview; self.preferred_mode = Some(mode); CmdResult::Keep } Err(e) => { CmdResult::DisplayError( format!("Can't display as {mode:?} : {e:?}") ) } }) } fn no_opt_selection(&self) -> Selection<'_> { match self.transform.as_ref() { // When there's a transform, we can't assume the line number makes sense Some(transform) => Selection { path: &transform.output_path, stype: SelectionType::File, is_exe: false, line: 0, }, None => Selection { path: &self.source_path, stype: SelectionType::File, is_exe: false, line: self.vis_preview().get_selected_line_number().unwrap_or(0), }, } } } impl PanelState for PreviewState { fn get_type(&self) -> PanelStateType { PanelStateType::Preview } fn set_mode(&mut self, mode: Mode) { self.mode = mode; } fn get_mode(&self) -> Mode { self.mode } fn get_pending_task(&self) -> Option<&'static str> { if self.pending_pattern.is_some() { Some("searching") } else { None } } fn on_pattern( &mut self, pat: InputPattern, _app_state: &AppState, _con: &AppContext, ) -> Result { if pat.is_none() { if let Some(filtered_preview) = self.filtered_preview.take() { let old_selection = filtered_preview.get_selected_line_number(); if let Some(number) = old_selection { self.preview.try_select_line_number(number); } self.removed_pattern = filtered_preview.pattern(); } } else if !self.preview.is_filterable() { return Ok(CmdResult::error("this preview can't be searched")); } self.pending_pattern = pat; Ok(CmdResult::Keep) } /// do the preview filtering if required and not yet done fn do_pending_task( &mut self, _app_state: &mut AppState, _screen: Screen, con: &AppContext, dam: &mut Dam, ) -> Result<(), ProgramError> { if self.pending_pattern.is_some() { let old_selection = self .filtered_preview .as_ref() .and_then(|p| p.get_selected_line_number()) .or_else(|| self.preview.get_selected_line_number()); let pattern = self.pending_pattern.take(); self.filtered_preview = time!( Info, "preview filtering", self.preview.filtered(self.preview_path(), pattern, dam, con), ); // can be None if a cancellation was required if let Some(ref mut filtered_preview) = self.filtered_preview { if let Some(number) = old_selection { filtered_preview.try_select_line_number(number); } } } Ok(()) } fn selected_path(&self) -> Option<&Path> { Some(&self.source_path) } fn set_selected_path(&mut self, path: PathBuf, con: &AppContext) { let selected_line_number = if self.preview_path() == path { self.preview.get_selected_line_number() } else { None }; if let Some(fp) = &self.filtered_preview { self.pending_pattern = fp.pattern(); }; self.transform = con.preview_transformers.transform(&path, self.preferred_mode); let preview_path = self.transform.as_ref().map_or(&path, |c| &c.output_path); self.preview = Preview::new(preview_path, self.preferred_mode, con); if let Some(number) = selected_line_number { self.preview.try_select_line_number(number); } self.source_path = path; } fn selection(&self) -> Option> { Some(self.no_opt_selection()) } fn tree_options(&self) -> TreeOptions { self.tree_options.clone() } fn with_new_options( &mut self, _screen: Screen, change_options: &dyn Fn(&mut TreeOptions) -> &'static str, _in_new_panel: bool, // TODO open tree if true _con: &AppContext, ) -> CmdResult { change_options(&mut self.tree_options); CmdResult::Keep } fn refresh(&mut self, _screen: Screen, con: &AppContext) -> Command { self.dirty = true; self.set_selected_path(self.source_path.clone(), con); Command::empty() } fn on_click( &mut self, _x: u16, y: u16, _screen: Screen, _con: &AppContext, ) -> Result { if y >= self.preview_area.top && y < self.preview_area.top + self.preview_area.height { let y = y - self.preview_area.top; self.mut_preview().try_select_y(y); } Ok(CmdResult::Keep) } fn display( &mut self, w: &mut W, disc: &DisplayContext, ) -> Result<(), ProgramError> { let state_area = &disc.state_area; if state_area.height < 3 { warn!("area too small for preview"); return Ok(()); } let mut preview_area = state_area.clone(); preview_area.height -= 1; preview_area.top += 1; if preview_area != self.preview_area { self.dirty = true; self.preview_area = preview_area; } if self.dirty { disc.panel_skin.styles.default.queue_bg(w)?; disc.screen.clear_area_to_right(w, state_area)?; self.dirty = false; } let styles = &disc.panel_skin.styles; w.queue(cursor::MoveTo(state_area.left, 0))?; let mut cw = CropWriter::new(w, state_area.width as usize); let file_name = self .source_path .file_name() .map(|n| n.to_string_lossy().to_string()) .unwrap_or_else(|| "???".to_string()); cw.queue_str(&styles.preview_title, &file_name)?; let info_area = Area::new( state_area.left + state_area.width - cw.allowed as u16, state_area.top, cw.allowed as u16, 1, ); cw.fill(&styles.preview_title, &SPACE_FILLING)?; let preview = self.filtered_preview.as_mut().unwrap_or(&mut self.preview); preview.display_info(w, disc.screen, disc.panel_skin, &info_area)?; if let Err(err) = preview.display(w, disc, &self.preview_area) { warn!("error while displaying file: {:?}", &err); if preview.get_mode().is_some() { // means it's not an error already if let ProgramError::Io { source } = err { // we mutate the preview to Preview::IOError self.preview = Preview::IoError(source); return self.display(w, disc); } } return Err(err); } Ok(()) } fn no_verb_status( &self, has_previous_state: bool, con: &AppContext, width: usize, // available width ) -> Status { let mut ssb = con.standard_status.builder( PanelStateType::Preview, self.no_opt_selection(), width, ); ssb.has_previous_state = has_previous_state; ssb.is_filtered = self.filtered_preview.is_some(); ssb.has_removed_pattern = self.removed_pattern.is_some(); ssb.status() } fn on_internal( &mut self, w: &mut W, invocation_parser: Option<&InvocationParser>, internal_exec: &InternalExecution, input_invocation: Option<&VerbInvocation>, trigger_type: TriggerType, app_state: &mut AppState, cc: &CmdContext, ) -> Result { let con = &cc.app.con; match internal_exec.internal { Internal::back => { if self.filtered_preview.is_some() { self.on_pattern(InputPattern::none(), app_state, con) } else { Ok(CmdResult::PopState) } } Internal::copy_line => { #[cfg(not(feature = "clipboard"))] { Ok(CmdResult::error("Clipboard feature not enabled at compilation")) } #[cfg(feature = "clipboard")] { Ok(match self.mut_preview().get_selected_line() { Some(line) => { match terminal_clipboard::set_string(line) { Ok(()) => CmdResult::Keep, Err(_) => CmdResult::error("Clipboard error while copying path"), } } None => CmdResult::error("No selected line in preview"), }) } } Internal::line_down => { let count = get_arg(input_invocation, internal_exec, 1); self.mut_preview().move_selection(count, true); Ok(CmdResult::Keep) } Internal::line_up => { let count = get_arg(input_invocation, internal_exec, 1); self.mut_preview().move_selection(-count, true); Ok(CmdResult::Keep) } Internal::line_down_no_cycle => { let count = get_arg(input_invocation, internal_exec, 1); self.mut_preview().move_selection(count, false); Ok(CmdResult::Keep) } Internal::line_up_no_cycle => { let count = get_arg(input_invocation, internal_exec, 1); self.mut_preview().move_selection(-count, false); Ok(CmdResult::Keep) } Internal::page_down => { self.mut_preview().try_scroll(ScrollCommand::Pages(1)); Ok(CmdResult::Keep) } Internal::page_up => { self.mut_preview().try_scroll(ScrollCommand::Pages(-1)); Ok(CmdResult::Keep) } //Internal::restore_pattern => { // debug!("restore_pattern"); // self.pending_pattern = self.removed_pattern.take(); // Ok(CmdResult::Keep) //} Internal::panel_left if self.removed_pattern.is_some() => { self.pending_pattern = self.removed_pattern.take(); Ok(CmdResult::Keep) } Internal::panel_left_no_open if self.removed_pattern.is_some() => { self.pending_pattern = self.removed_pattern.take(); Ok(CmdResult::Keep) } Internal::panel_right if self.filtered_preview.is_some() => { self.on_pattern(InputPattern::none(), app_state, con) } Internal::panel_right_no_open if self.filtered_preview.is_some() => { self.on_pattern(InputPattern::none(), app_state, con) } Internal::select_first => { self.mut_preview().select_first(); Ok(CmdResult::Keep) } Internal::select_last => { self.mut_preview().select_last(); Ok(CmdResult::Keep) } Internal::previous_match => { self.mut_preview().previous_match(); Ok(CmdResult::Keep) } Internal::next_match => { self.mut_preview().next_match(); Ok(CmdResult::Keep) } Internal::preview_image => self.set_mode(PreviewMode::Image, con), Internal::preview_text => self.set_mode(PreviewMode::Text, con), Internal::preview_binary => self.set_mode(PreviewMode::Hex, con), _ => self.on_internal_generic( w, invocation_parser, internal_exec, input_invocation, trigger_type, app_state, cc, ), } } fn get_flags(&self) -> Vec { vec![] } fn get_starting_input(&self) -> String { if let Some(preview) = &self.filtered_preview { preview.pattern().raw } else { self.pending_pattern.raw.clone() } } } broot-1.46.3/src/preview/preview_transformer.rs000064400000000000000000000226641046102023000177770ustar 00000000000000use { crate::{ errors::*, preview::PreviewMode, }, serde::Deserialize, std::{ fs, hash::{ DefaultHasher, Hash, Hasher, }, path::{ Path, PathBuf, }, process::Command, }, tempfile::TempDir, }; #[derive(Debug, Clone, Copy)] pub struct TransformerId { idx: usize, } pub struct PreviewTransformers { transformers: Vec, /// Where the output files are temporarily stored temp_dir: TempDir, } #[derive(Debug, Clone, Deserialize)] pub struct PreviewTransformerConf { pub input_extensions: Vec, pub output_extension: String, /// The command generating an output file from an input file /// eg "mutool draw -o {output-path} {input-path}" pub command: Vec, pub mode: PreviewMode, } #[derive(Debug, Clone)] pub struct PreviewTransformer { pub input_extensions: Vec, pub output_extension: String, /// The command generating an output file from an input file /// eg "mutool draw -o {output-path} {input-path}" pub command: Vec, pub mode: PreviewMode, pub input_kind: ProcessInputKind, pub output_kind: ProcessOutputKind, } /// Specified how the input of the transformation is provided to the /// external process. #[derive(Debug, Clone, Copy)] pub enum ProcessInputKind { File, Stdin, } /// Specifies how the output of the transformation is read: /// - read from {output-path} if it's in the command, or /// - read from the first file found in {output-dir} if it's in the command, or /// - read from stdout if neither is in the command #[derive(Debug, Clone, Copy)] pub enum ProcessOutputKind { File, Dir, Stdout, } pub struct PreviewTransform { pub transformer_id: TransformerId, /// Path to the generated file pub output_path: PathBuf, } impl PreviewTransformers { pub fn new(transformer_confs: &[PreviewTransformerConf]) -> Result { let mut transformers = Vec::with_capacity(transformer_confs.len()); for transformer_conf in transformer_confs { transformers.push(PreviewTransformer::from_conf(transformer_conf)?); } let temp_dir = tempfile::Builder::new() .prefix("broot-conversions") .tempdir()?; Ok(Self { transformers, temp_dir, }) } pub fn transformer( &self, id: TransformerId, ) -> &PreviewTransformer { &self.transformers[id.idx] } pub fn transform( &self, input_path: &Path, mode: Option, ) -> Option { let transformer_id = self.find_transformer_for(input_path, mode)?; let temp_dir = self.temp_dir.path(); match self.transformers[transformer_id.idx].transform(input_path, temp_dir) { Ok(output_path) => Some(PreviewTransform { transformer_id, output_path, }), Err(e) => { error!( "conversion failed using {:?}", self.transformers[transformer_id.idx].command ); error!("conversion error: {:?}", e); None } } } pub fn find_transformer_for( &self, path: &Path, mode: Option, ) -> Option { let extension = path.extension().and_then(|ext| ext.to_str())?; for (idx, transformer) in self.transformers.iter().enumerate() { if !transformer .input_extensions .iter() .any(|ext| ext.eq_ignore_ascii_case(extension)) { continue; } if let Some(mode) = mode { if transformer.mode != mode { continue; } } return Some(TransformerId { idx }); } None } } impl PreviewTransformer { pub fn from_conf(conf: &PreviewTransformerConf) -> Result { if conf.command.is_empty() { return Err(ConfError::MissingField { txt: "empty command in preview transformer".to_string(), }); } let has_input_path = conf.command.iter().any(|c| c.contains("{input-path}")); let has_output_path = conf.command.iter().any(|c| c.contains("{output-path}")); let has_output_dir = conf.command.iter().any(|c| c.contains("{output-dir}")); let input_kind = if has_input_path { ProcessInputKind::File } else { ProcessInputKind::Stdin }; let output_kind = if has_output_path { ProcessOutputKind::File } else if has_output_dir { ProcessOutputKind::Dir } else { ProcessOutputKind::Stdout }; Ok(Self { input_extensions: conf.input_extensions.clone(), output_extension: conf.output_extension.clone(), command: conf.command.clone(), mode: conf.mode, input_kind, output_kind, }) } /// Call the external process to transform the input file into an output file /// /// Input is given to the process either as a file or as stdin, depending on /// whether the command contains "{input-path}". /// /// Output is /// - read from {output-path} if it's in the command, or /// - read from the first file found in {output-dir} if it's in the command, or /// - read from stdout if neither is in the command pub fn transform( &self, input_path: &Path, temp_dir: &Path, ) -> Result { let hash = { let mut hasher = DefaultHasher::new(); input_path.hash(&mut hasher); hasher.finish() }; let input_stem = input_path .file_stem() .ok_or(PreviewTransformerError::InvalidInput)? .to_string_lossy(); let output_dir = temp_dir.join(format!("{:x}", hash)); if output_dir.exists() { // if there's a file in the output directory, it's the result of a previous // transformation of the same input file if let Some(path) = first_file_in_dir(&output_dir)? { // we check that the transformed file isn't older than the file // to preview (or changes would be ignored) let input_modified = input_path.metadata().and_then(|m| m.modified()); let transformed_modified = path.metadata().and_then(|m| m.modified()); match (input_modified, transformed_modified) { (Ok(input_date), Ok(transformed_date)) if input_date <= transformed_date => { // the transformed file is up to date debug!("preview transform {:?} up to date", path); return Ok(path); } _ => { // the transformed file is obsolete debug!("preview transform {:?} obsolete", path); fs::remove_file(&path)?; } } } } else { fs::create_dir(&output_dir)?; } let mut output_path = output_dir.join(format!("{}.{}", input_stem, self.output_extension)); let mut command = self.command.iter().map(|part| { part.replace("{input-path}", &input_path.to_string_lossy()) .replace("{output-dir}", &output_dir.to_string_lossy()) .replace("{output-path}", &output_path.to_string_lossy()) }); info!("transforming {:?} to {:?}", input_path, output_path); let executable = command.next().unwrap(); let mut process = Command::new(executable); process.stderr(std::process::Stdio::null()); process.args(command); match self.input_kind { ProcessInputKind::File => { process.stdin(std::process::Stdio::null()); } ProcessInputKind::Stdin => { process.stdin(std::fs::File::open(input_path)?); } } match self.output_kind { ProcessOutputKind::File | ProcessOutputKind::Dir => { process.stdout(std::process::Stdio::null()); } ProcessOutputKind::Stdout => { process.stdout(std::fs::File::create(&output_path)?); } } let exit_status = process.spawn().and_then(|mut p| p.wait())?; output_path = first_file_in_dir(&output_dir)?.ok_or(PreviewTransformerError::NoOutput)?; if exit_status.success() { Ok(output_path) } else { // we remove the output file if the process failed, so that // it's not returned on the next call let _ = std::fs::remove_file(&output_path); match exit_status.code() { Some(code) => Err(PreviewTransformerError::ProcessFailed { code }), None => Err(PreviewTransformerError::ProcessInterrupted), } } } } fn first_file_in_dir(dir: &Path) -> Result, PreviewTransformerError> { for entry in fs::read_dir(dir)? { let entry = entry?; let path = entry.path(); if path.is_file() { return Ok(Some(path)); } } Ok(None) } broot-1.46.3/src/preview/zero_len_file_view.rs000064400000000000000000000030331046102023000175270ustar 00000000000000use { crate::{ display::{Screen, W}, errors::ProgramError, skin::PanelSkin, }, char_reader::CharReader, crokey::crossterm::{ cursor, QueueableCommand, }, std::{ fs::File, path::PathBuf, }, termimad::{Area, CropWriter, SPACE_FILLING}, }; /// a (light) display for a file declaring a size 0, /// as happens for many system "files", for example in /proc pub struct ZeroLenFileView { path: PathBuf, } impl ZeroLenFileView { pub fn new(path: PathBuf) -> Self { Self { path } } pub fn display( &mut self, w: &mut W, _screen: Screen, panel_skin: &PanelSkin, area: &Area, ) -> Result<(), ProgramError> { let styles = &panel_skin.styles; let line_count = area.height as usize; let file = File::open(&self.path)?; let mut reader = CharReader::new(file); // line_len here is in chars, and we crop in cols, but it's OK because both // are usually identical for system files and we crop later anyway let line_len = area.width as usize; for y in 0..line_count { w.queue(cursor::MoveTo(area.left, y as u16 + area.top))?; let mut cw = CropWriter::new(w, area.width as usize); let cw = &mut cw; if let Some(line) = reader.next_line(line_len, 15_000)? { cw.queue_str(&styles.default, &line)?; } cw.fill(&styles.default, &SPACE_FILLING)?; } Ok(()) } } broot-1.46.3/src/print.rs000064400000000000000000000054721046102023000133450ustar 00000000000000//! functions printing a tree or a path use { crate::{ app::*, display::Screen, errors::ProgramError, launchable::Launchable, skin::{PanelSkin, StyleMap}, tree::Tree, }, crokey::crossterm::tty::IsTty, pathdiff, std::{ io::{self, stdout}, path::Path, }, }; fn print_string(string: String, _con: &AppContext) -> io::Result { Ok( // We write on stdout, but we must do it after app closing // to have the desired stdout (it may be the normal terminal // or a file, or other output) CmdResult::from(Launchable::printer(string)) ) } pub fn print_paths(sel_info: SelInfo, con: &AppContext) -> io::Result { let string = match sel_info { SelInfo::None => "".to_string(), // better idea ? SelInfo::One(sel) => sel.path.to_string_lossy().to_string(), SelInfo::More(stage) => { let mut string = String::new(); for path in stage.paths() { string.push_str(&path.to_string_lossy()); string.push('\n'); } string } }; print_string(string, con) } fn relativize_path(path: &Path, con: &AppContext) -> io::Result { let relative_path = match pathdiff::diff_paths(path, &con.initial_root) { None => { return Err(io::Error::new( io::ErrorKind::Other, format!("Cannot relativize {path:?}"), // does this happen ? how ? )); } Some(p) => p, }; Ok( if relative_path.components().next().is_some() { relative_path.to_string_lossy().to_string() } else { ".".to_string() } ) } pub fn print_relative_paths(sel_info: SelInfo, con: &AppContext) -> io::Result { let string = match sel_info { SelInfo::None => "".to_string(), SelInfo::One(sel) => relativize_path(sel.path, con)?, SelInfo::More(stage) => { let mut string = String::new(); for path in stage.paths() { string.push_str(&relativize_path(path, con)?); string.push('\n'); } string } }; print_string(string, con) } pub fn print_tree( tree: &Tree, screen: Screen, panel_skin: &PanelSkin, con: &AppContext, ) -> Result { // We write on stdout, but we must do it after app closing to have the normal terminal let show_color = con.launch_args.color.unwrap_or_else(|| stdout().is_tty()); let styles = if show_color { panel_skin.styles.clone() } else { StyleMap::no_term() }; Ok(CmdResult::from(Launchable::tree_printer( tree, screen, styles, con.ext_colors.clone(), ))) } broot-1.46.3/src/shell_install/bash.rs000064400000000000000000000123701046102023000157560ustar 00000000000000//! The goal of this mod is to ensure the launcher shell function //! is available for bash and zsh i.e. the `br` shell function can //! be used to launch broot (and thus make it possible to execute //! some commands, like `cd`, from the starting shell. //! //! In a correct installation, we have: //! - a function declaration script in ~/.local/share/broot/launcher/bash/br/1 //! - a link to that script in ~/.config/broot/launcher/bash/br/1 //! - a line to source the link in ~/.bashrc and ~/.zshrc //! //! (exact paths depend on XDG variables) use { super::{util, ShellInstall}, crate::{ conf, errors::*, }, directories::UserDirs, lazy_regex::regex, regex::Captures, std::{env, path::PathBuf}, termimad::{ mad_print_inline, }, }; const NAME: &str = "bash"; const SOURCING_FILES: &[&str] = &[".bashrc", ".bash_profile", ".zshrc", "$ZDOTDIR/.zshrc"]; const VERSION: &str = "1"; // This script has been tested on bash and zsh. // It's installed under the bash name (~/.config/broot // but linked from both the .bashrc and the .zshrc files const BASH_FUNC: &str = r#" # This script was automatically generated by the broot program # More information can be found in https://github.com/Canop/broot # This function starts broot and executes the command # it produces, if any. # It's needed because some shell commands, like `cd`, # have no useful effect if executed in a subshell. function br { local cmd cmd_file code cmd_file=$(mktemp) if broot --outcmd "$cmd_file" "$@"; then cmd=$(<"$cmd_file") command rm -f "$cmd_file" eval "$cmd" else code=$? command rm -f "$cmd_file" return "$code" fi } "#; const MD_NO_SOURCING: &str = r" I found no sourcing file for the bash/zsh family. If you're using bash or zsh, then installation isn't complete: the br function initialization script won't be sourced unless you source it yourself. "; pub fn get_script() -> &'static str { BASH_FUNC } /// return the path to the link to the function script fn get_link_path() -> PathBuf { conf::dir().join("launcher").join(NAME).join("br") } /// return the path to the script containing the function. /// /// At version 0.10.4 we change the location of the script: /// It was previously with the link, but it's now in /// XDG_DATA_HOME (typically ~/.local/share on linux) fn get_script_path() -> PathBuf { conf::app_dirs() .data_dir() .join("launcher") .join(NAME) .join(VERSION) } /// return the paths to the files in which the br function is sourced. /// Paths in SOURCING_FILES can be absolute or relative to the home /// directory. Environment variables designed as $NAME are interpolated. fn get_sourcing_paths() -> Vec { let homedir_path = UserDirs::new() .expect("no home directory!") .home_dir() .to_path_buf(); SOURCING_FILES .iter() .map(|name| { regex!(r#"\$(\w+)"#) .replace(name, |c: &Captures<'_>| { env::var(&c[1]).unwrap_or_else(|_| (*name).to_string()) }) .to_string() }) .map(PathBuf::from) .map(|path| { if path.is_absolute() { path } else { homedir_path.join(path) } }) .filter(|path| { debug!("considering path: {:?}", &path); path.exists() }) .collect() } /// check for bash and zsh shells. /// check whether the shell function is installed, install /// it if it wasn't refused before or if broot is launched /// with --install. pub fn install(si: &mut ShellInstall) -> Result<(), ShellInstallError> { let script_path = get_script_path(); si.write_script(&script_path, BASH_FUNC)?; let link_path = get_link_path(); si.create_link(&link_path, &script_path)?; let sourcing_paths = get_sourcing_paths(); if sourcing_paths.is_empty() { warn!("no sourcing path for bash/zsh!"); si.skin.print_text(MD_NO_SOURCING); return Ok(()); } let escaped_path = link_path.to_string_lossy().replace(' ', "\\ "); let source_line = format!("source {}", &escaped_path); for sourcing_path in &sourcing_paths { let sourcing_path_str = sourcing_path.to_string_lossy(); if util::file_contains_line(sourcing_path, &source_line)? { mad_print_inline!( &si.skin, "`$0` already patched, no change made.\n", &sourcing_path_str, ); } else { util::append_to_file(sourcing_path, format!("\n{source_line}\n"))?; let is_zsh = sourcing_path_str.contains(".zshrc"); if is_zsh { mad_print_inline!( &si.skin, "`$0` successfully patched, you can make the function immediately available with `exec zsh`\n", &sourcing_path_str, ); } else { mad_print_inline!( &si.skin, "`$0` successfully patched, you can make the function immediately available with `source $0`\n", &sourcing_path_str, ); } } } si.done = true; Ok(()) } broot-1.46.3/src/shell_install/fish.rs000064400000000000000000000064671046102023000160040ustar 00000000000000//! The goal of this mod is to ensure the launcher shell function //! is available for fish i.e. the `br` shell function can //! be used to launch broot (and thus make it possible to execute //! some commands, like `cd`, from the starting shell. //! //! //! In a correct installation, we have: //! - a function declaration script in ~/.local/share/broot/launcher/fish/br.fish //! - a link to that script in ~/.config/fish/functions/br.fish //! //! (exact paths depend on XDG variables) //! //! fish stores functions in FISH_CONFIG_DIR/functions (for example, //! ~/.config/fish/functions) and lazily loads (or reloads) them as //! needed. use { super::ShellInstall, crate::{conf, errors::*}, directories::BaseDirs, directories::ProjectDirs, std::path::PathBuf, }; const NAME: &str = "fish"; const SCRIPT_FILENAME: &str = "br.fish"; const FISH_FUNC: &str = r" # This script was automatically generated by the broot program # More information can be found in https://github.com/Canop/broot # This function starts broot and executes the command # it produces, if any. # It's needed because some shell commands, like `cd`, # have no useful effect if executed in a subshell. function br --wraps=broot set -l cmd_file (mktemp) if broot --outcmd $cmd_file $argv source $cmd_file rm -f $cmd_file else set -l code $status rm -f $cmd_file return $code end end "; pub fn get_script() -> &'static str { FISH_FUNC } /// return the root of fish's config fn get_fish_dir() -> PathBuf { if let Some(base_dirs) = BaseDirs::new() { let fish_dir = base_dirs.home_dir().join(".config/fish"); if fish_dir.exists() { return fish_dir; } } ProjectDirs::from("fish", "fish", "fish") // hem... .expect("Unable to find configuration directories") .config_dir() .to_path_buf() } /// return the fish functions directory fn get_fish_functions_dir() -> PathBuf { get_fish_dir().join("functions") } /// return the path to the link to the function script /// /// At version 0.10.4 we change the location of the script: /// It was previously with the link, but it's now in /// ~/.config/fish/functions/br.fish fn get_link_path() -> PathBuf { get_fish_functions_dir().join("br.fish") } /// return the path to the script containing the function. /// /// At version 0.10.4 we change the location of the script: /// It was previously with the link, but it's now in /// ~/.local/share/broot/launcher/fish/br.fish fn get_script_path() -> PathBuf { conf::app_dirs() .data_dir() .join("launcher") .join(NAME) .join(SCRIPT_FILENAME) } /// check for fish shell /// /// As fish isn't frequently used, we first check that it seems /// to be installed. If not, we just do nothing. pub fn install(si: &mut ShellInstall) -> Result<(), ShellInstallError> { let fish_dir = get_fish_dir(); if !fish_dir.exists() { debug!("no fish config directory. Assuming fish isn't used."); return Ok(()); } info!("fish seems to be installed"); let script_path = get_script_path(); si.write_script(&script_path, FISH_FUNC)?; let link_path = get_link_path(); // creating the link may create the fish/conf.d directory si.create_link(&link_path, &script_path)?; si.done = true; Ok(()) } broot-1.46.3/src/shell_install/mod.rs000064400000000000000000000175361046102023000156310ustar 00000000000000mod bash; mod fish; mod nushell; mod state; mod util; mod powershell; use { crate::{ cli, errors::*, skin, }, std::{ fs, os, path::Path, }, termimad::{mad_print_inline, MadSkin}, }; pub use { state::ShellInstallState, }; const MD_INSTALL_REQUEST: &str = r#" **Broot** should be launched using a shell function. This function most notably makes it possible to `cd` from inside broot (see *https://dystroy.org/broot/install-br/* for explanations). Can I install it now? [**Y**/n] "#; const MD_UPGRADE_REQUEST: &str = r#" Broot's shell function should be upgraded. Can I proceed? [**Y**/n] "#; const MD_INSTALL_CANCELLED: &str = r#" You refused the installation (for now). You can still used `broot` but some features won't be available. If you want the `br` shell function, you may either * do `broot --install` * install the various pieces yourself (see *https://dystroy.org/broot/install-br/* for details). "#; const MD_PERMISSION_DENIED: &str = r#" Installation check resulted in **Permission Denied**. Please relaunch with elevated privilege. This is typically only needed once. Error details: "#; const MD_INSTALL_DONE: &str = r#" The **br** function has been successfully installed. You may have to restart your shell or source your shell init files. Afterwards, you should start broot with `br` in order to use its full power. "#; pub struct ShellInstall { force_install: bool, // when the program was launched with --install skin: MadSkin, pub should_quit: bool, authorization: Option, done: bool, // true if the installation was just made } impl ShellInstall { pub fn new(force_install: bool) -> Self { Self { force_install, skin: skin::make_cli_mad_skin(), should_quit: false, authorization: if force_install { Some(true) } else { None }, done: false, } } /// write on stdout the script building the function for /// the given shell pub fn print(shell: &str) -> Result<(), ProgramError> { match shell { "bash" | "zsh" => println!("{}", bash::get_script()), "fish" => println!("{}", fish::get_script()), "nushell" => println!("{}", nushell::get_script()), "powershell" => println!("{}", powershell::get_script()), _ => { return Err(ProgramError::UnknowShell { shell: shell.to_string(), }); } } Ok(()) } /// check whether the shell function is installed an up to date, /// install it if it wasn't refused before or if broot is launched /// with --install. pub fn check(&mut self) -> Result<(), ShellInstallError> { let install_state = ShellInstallState::detect(); info!("Shell installation state: {install_state:?}"); if self.force_install { self.skin.print_text("You requested a clean (re)install."); ShellInstallState::remove(self)?; } else { match install_state { ShellInstallState::Refused => { return Ok(()); } ShellInstallState::UpToDate => { return Ok(()); } ShellInstallState::Obsolete => { if !self.can_upgrade()? { debug!("User refuses the upgrade. Doing nothing."); return Ok(()); } } ShellInstallState::NotInstalled => { if !self.can_install()? { debug!("User refuses the installation. Doing nothing."); return Ok(()); } } } } // even if the installation isn't really complete (for example // when no bash file was found), we don't want to ask the user // again, we'll assume it's done ShellInstallState::UpToDate.write(self)?; debug!("Starting install"); bash::install(self)?; fish::install(self)?; nushell::install(self)?; powershell::install(self)?; self.should_quit = true; if self.done { self.skin.print_text(MD_INSTALL_DONE); } Ok(()) } /// print some additional information on the error (typically before /// the error itself is dumped) pub fn comment_error(&self, err: &ShellInstallError) { if err.is_permission_denied() { self.skin.print_text(MD_PERMISSION_DENIED); } } pub fn remove(&self, path: &Path) -> Result<(), ShellInstallError> { // path.exists() doesn't work when the file is a link (it checks whether // the link destination exists instead of checking the link exists // so we first check whether the link exists if fs::read_link(path).is_ok() || path.exists() { mad_print_inline!(self.skin, "Removing `$0`.\n", path.to_string_lossy()); fs::remove_file(path) .context(&|| format!("removing {path:?}"))?; } Ok(()) } /// check whether we're allowed to install. fn can_install(&mut self) -> Result { self.can_do(false) } fn can_upgrade(&mut self) -> Result { self.can_do(true) } fn can_do(&mut self, upgrade: bool) -> Result { if let Some(authorization) = self.authorization { return Ok(authorization); } let refused_path = ShellInstallState::get_refused_path(); if refused_path.exists() { debug!("User already refused the installation"); return Ok(false); } self.skin.print_text(if upgrade { MD_UPGRADE_REQUEST } else { MD_INSTALL_REQUEST }); let proceed = cli::ask_authorization() .context(&|| "asking user".to_string())?; // read_line failure debug!("proceed: {:?}", proceed); self.authorization = Some(proceed); if !proceed { ShellInstallState::Refused.write(self)?; self.skin.print_text(MD_INSTALL_CANCELLED); } Ok(proceed) } /// write the script at the given path fn write_script(&self, script_path: &Path, content: &str) -> Result<(), ShellInstallError> { self.remove(script_path)?; info!("Writing `br` shell function in `{:?}`", &script_path); mad_print_inline!( &self.skin, "Writing *br* shell function in `$0`.\n", script_path.to_string_lossy(), ); fs::create_dir_all(script_path.parent().unwrap()) .context(&|| format!("creating parent dirs to {script_path:?}"))?; fs::write(script_path, content) .context(&|| format!("writing script in {script_path:?}"))?; Ok(()) } /// create a link fn create_link(&self, link_path: &Path, script_path: &Path) -> Result<(), ShellInstallError> { info!("Creating link from {:?} to {:?}", &link_path, &script_path); self.remove(link_path)?; let link_path_str = link_path.to_string_lossy(); let script_path_str = script_path.to_string_lossy(); mad_print_inline!( &self.skin, "Creating link from `$0` to `$1`.\n", &link_path_str, &script_path_str, ); let parent = link_path.parent().unwrap(); fs::create_dir_all(parent) .context(&|| format!("creating directory {parent:?}"))?; #[cfg(unix)] os::unix::fs::symlink(script_path, link_path) .context(&|| format!("linking from {link_path:?} to {script_path:?}"))?; #[cfg(windows)] os::windows::fs::symlink_file(&script_path, &link_path) .context(&|| format!("linking from {link_path:?} to {script_path:?}"))?; Ok(()) } } broot-1.46.3/src/shell_install/nushell.rs000064400000000000000000000306241046102023000165150ustar 00000000000000//! The goal of this mod is to ensure the launcher shell function //! is available for nushell i.e. the `br` shell function can //! be used to launch broot (and thus make it possible to execute //! some commands, like `cd`, from the starting shell. //! //! In a correct installation, we have: //! - a function declaration script in ~/.local/share/broot/launcher/nushell/br/1 //! - a link to that script in ~/.config/broot/launcher/nushell/br/1 //! - a line to use the link in ~/.config/nushell/config.nu //! //! (exact paths depend on XDG variables) //! //! Please note that this function doesn't allow other commands than cd, //! contrary to the similar function of other shells. use { super::{util, ShellInstall}, crate::{conf, errors::*}, directories::BaseDirs, std::path::PathBuf, termimad::mad_print_inline, }; const NAME: &str = "nushell"; const VERSION: &str = "6"; const NU_FUNC: &str = r#" # Launch broot # # Examples: # > br -hi some/path # > br # > br -sdp # > br -hi -c "vacheblan.svg;:open_preview" .. # # See https://dystroy.org/broot/install-br/ export def --env br [ --cmd(-c): string # Semicolon separated commands to execute --color: string = "auto" # Whether to have styles and colors (auto is default and usually OK) [possible values: auto, yes, no] --conf: string # Semicolon separated paths to specific config files"), --dates(-d) # Show the last modified date of files and directories" --no-dates(-D) # Don't show the last modified date" --only-folders(-f) # Only show folders --no-only-folders(-F) # Show folders and files alike --show-git-info(-g) # Show git statuses on files and stats on repo --no-show-git-info(-G) # Don't show git statuses on files and stats on repo --git-status # Only show files having an interesting git status, including hidden ones --hidden(-h) # Show hidden files --no-hidden(-H) # Don't show hidden files --height: int # Height (if you don't want to fill the screen or for file export) --help # Print help information --git-ignored(-i) # Show git ignored files --no-git-ignored(-I) # Don't show git ignored files --install # Install or reinstall the br shell function --no-sort # Don't sort --permissions(-p) # Show permissions --no-permissions(-P) # Don't show permissions --print-shell-function: string # Print to stdout the br function for a given shell --sizes(-s) # Show the size of files and directories --no-sizes(-S) # Don't show sizes --set-install-state: path # Where to write the produced cmd (if any) [possible values: undefined, refused, installed] --show-root-fs # Show filesystem info on top --max-depth: int # Only show trees up to a certain depth --sort-by-count # Sort by count (only show one level of the tree) --sort-by-date # Sort by date (only show one level of the tree) --sort-by-size # Sort by size (only show one level of the tree) --sort-by-type # Same as sort-by-type-dirs-first --sort-by-type-dirs-first # Sort by type, directories first (only show one level of the tree) --sort-by-type-dirs-last # Sort by type, directories last (only show one level of the tree) --trim-root(-t) # Trim the root too and don't show a scrollbar --no-trim-root(-T) # Don't trim the root level, show a scrollbar --version(-V) # Print version information --whale-spotting(-w) # Sort by size, show ignored and hidden files --write-default-conf: path # Write default conf files in given directory file?: path # Root Directory ] { mut args = [] if $cmd != null { $args = ($args | append $'--cmd=($cmd)') } if $color != null { $args = ($args | append $'--color=($color)') } if $conf != null { $args = ($args | append $'--conf=($conf)') } if $dates { $args = ($args | append $'--dates') } if $no_dates { $args = ($args | append $'--no-dates') } if $only_folders { $args = ($args | append $'--only-folders') } if $no_only_folders { $args = ($args | append $'--no-only-folders') } if $show_git_info { $args = ($args | append $'--show-git-info') } if $no_show_git_info { $args = ($args | append $'--no-show-git-info') } if $git_status { $args = ($args | append $'--git-status') } if $hidden { $args = ($args | append $'--hidden') } if $no_hidden { $args = ($args | append $'--no-hidden') } if $height != null { $args = ($args | append $'--height=($height)') } if $help { $args = ($args | append $'--help') } if $git_ignored { $args = ($args | append $'--git-ignored') } if $no_git_ignored { $args = ($args | append $'--no-git-ignored') } if $install { $args = ($args | append $'--install') } if $no_sort { $args = ($args | append $'--no-sort') } if $permissions { $args = ($args | append $'--permissions') } if $no_permissions { $args = ($args | append $'--no-permissions') } if $print_shell_function != null { $args = ($args | append $'--print-shell-function=($print_shell_function)') } if $sizes { $args = ($args | append $'--sizes') } if $no_sizes { $args = ($args | append $'--no-sizes') } if $set_install_state != null { $args = ($args | append $'--set-install-state=($set_install_state)') } if $show_root_fs { $args = ($args | append $'--show-root-fs') } if $max_depth != null { $args = ($args | append $'--max-depth=($max_depth)') } if $sort_by_count { $args = ($args | append $'--sort-by-count') } if $sort_by_date { $args = ($args | append $'--sort-by-date') } if $sort_by_size { $args = ($args | append $'--sort-by-size') } if $sort_by_type { $args = ($args | append $'--sort-by-type') } if $sort_by_type_dirs_first { $args = ($args | append $'--sort-by-type-dirs-first') } if $sort_by_type_dirs_last { $args = ($args | append $'--sort-by-type-dirs-last') } if $trim_root { $args = ($args | append $'--trim-root') } if $no_trim_root { $args = ($args | append $'--no-trim-root') } if $version { $args = ($args | append $'--version') } if $whale_spotting { $args = ($args | append $'--whale-spotting') } if $write_default_conf != null { $args = ($args | append $'--write-default-conf=($write_default_conf)') } let cmd_file = ( if ($env.XDG_RUNTIME_DIR? | is-not-empty) { $env.XDG_RUNTIME_DIR } else { $nu.temp-path } | path join $"broot-(random chars).tmp" ) touch $cmd_file if ($file == null) { ^broot --outcmd $cmd_file ...$args } else { ^broot --outcmd $cmd_file ...$args $file } let $cmd = (open $cmd_file) rm -p -f $cmd_file if (not ($cmd | lines | is-empty)) { cd ($cmd | parse -r `^cd\s+(?"|'|)(?.+)\k[\s\r\n]*$` | get path | to text) } } export extern broot [ --cmd(-c): string # Semicolon separated commands to execute --color: string = "auto" # Whether to have styles and colors (auto is default and usually OK) [possible values: auto, yes, no] --conf: string # Semicolon separated paths to specific config files"), --dates(-d) # Show the last modified date of files and directories" --no-dates(-D) # Don't show the last modified date" --only-folders(-f) # Only show folders --no-only-folders(-F) # Show folders and files alike --show-git-info(-g) # Show git statuses on files and stats on repo --no-show-git-info(-G) # Don't show git statuses on files and stats on repo --git-status # Only show files having an interesting git status, including hidden ones --hidden(-h) # Show hidden files --no-hidden(-H) # Don't show hidden files --height: int # Height (if you don't want to fill the screen or for file export) --help # Print help information --git-ignored(-i) # Show git ignored files --no-git-ignored(-I) # Don't show git ignored files --install # Install or reinstall the br shell function --no-sort # Don't sort --outcmd: path # Write cd command in given path --permissions(-p) # Show permissions --no-permissions(-P) # Don't show permissions --print-shell-function: string # Print to stdout the br function for a given shell --sizes(-s) # Show the size of files and directories --no-sizes(-S) # Don't show sizes --set-install-state: path # Where to write the produced cmd (if any) [possible values: undefined, refused, installed] --show-root-fs # Show filesystem info on top --max-depth: int # Only show trees up to a certain depth --sort-by-count # Sort by count (only show one level of the tree) --sort-by-date # Sort by date (only show one level of the tree) --sort-by-size # Sort by size (only show one level of the tree) --sort-by-type # Same as sort-by-type-dirs-first --sort-by-type-dirs-first # Sort by type, directories first (only show one level of the tree) --sort-by-type-dirs-last # Sort by type, directories last (only show one level of the tree) --trim-root(-t) # Trim the root too and don't show a scrollbar --no-trim-root(-T) # Don't trim the root level, show a scrollbar --version(-V) # Print version information --whale-spotting(-w) # Sort by size, show ignored and hidden files --write-default-conf: path # Write default conf files in given directory file?: path # Root Directory ] "#; pub fn get_script() -> &'static str { NU_FUNC } /// return the path to the link to the function script fn get_link_path() -> PathBuf { conf::dir().join("launcher").join(NAME).join("br") } /// return the root of fn get_nushell_dir() -> Option { BaseDirs::new() .map(|base_dirs| base_dirs.config_dir().join("nushell")) .filter(|dir| dir.exists()) } /// return the path to the script containing the function. /// /// In XDG_DATA_HOME (typically ~/.local/share on linux) fn get_script_path() -> PathBuf { conf::app_dirs() .data_dir() .join("launcher") .join(NAME) .join(VERSION) } /// Check for nushell. /// /// Check whether the shell function is installed, install /// it if it wasn't refused before or if broot is launched /// with --install. pub fn install(si: &mut ShellInstall) -> Result<(), ShellInstallError> { info!("install {NAME}"); let Some(nushell_dir) = get_nushell_dir() else { debug!("no nushell config directory. Assuming nushell isn't used."); return Ok(()); }; info!("nushell seems to be installed"); let script_path = get_script_path(); si.write_script(&script_path, NU_FUNC)?; let link_path = get_link_path(); si.create_link(&link_path, &script_path)?; let escaped_path = link_path.to_string_lossy().replace(' ', "\\ "); let source_line = format!("use '{}' *", &escaped_path); let sourcing_path = nushell_dir.join("config.nu"); if !sourcing_path.exists() { warn!("Unexpected lack of config.nu file"); return Ok(()); } if sourcing_path.is_dir() { warn!("config.nu file"); return Ok(()); } let sourcing_path_str = sourcing_path.to_string_lossy(); if util::file_contains_line(&sourcing_path, &source_line)? { mad_print_inline!( &si.skin, "`$0` already patched, no change made.\n", &sourcing_path_str, ); } else { util::append_to_file(&sourcing_path, format!("\n{source_line}\n"))?; mad_print_inline!( &si.skin, "`$0` successfully patched, you can make the function immediately available with `use '$0' *`\n", &sourcing_path_str, ); } si.done = true; Ok(()) } broot-1.46.3/src/shell_install/powershell.rs000064400000000000000000000076021046102023000172270ustar 00000000000000//! The goal of this mod is to ensure the launcher shell function //! is available for PowerShell i.e. the `br` shell function can //! be used to launch broot (and thus make it possible to execute //! some commands, like `cd`, from the starting shell. //! //! In a correct installation, we have: //! - a function declaration script in %APPDATA%/dystroy/broot/data/launcher/powershell/1 //! - a link to that script in %APPDATA%/dystroy/broot/config/launcher/powershell/br.ps1 //! - a line to source the link in %USERPROFILE%/Documents/WindowsPowerShell/Profile.ps1 use { super::{util, ShellInstall}, crate::{conf, errors::*}, directories::UserDirs, std::{fs, path::PathBuf}, termimad::mad_print_inline, }; const NAME: &str = "powershell"; const VERSION: &str = "1"; const PS_FUNC: &str = r#" # https://github.com/Canop/broot/issues/460#issuecomment-1303005689 Function br { $args = $args -join ' ' $cmd_file = New-TemporaryFile $process = Start-Process -FilePath 'broot.exe' ` -ArgumentList "--outcmd $($cmd_file.FullName) $args" ` -NoNewWindow -PassThru -WorkingDirectory $PWD Wait-Process -InputObject $process #Faster than Start-Process -Wait If ($process.ExitCode -eq 0) { $cmd = Get-Content $cmd_file Remove-Item $cmd_file If ($cmd -ne $null) { Invoke-Expression -Command $cmd } } Else { Remove-Item $cmd_file Write-Host "`n" # Newline to tidy up broot unexpected termination Write-Error "broot.exe exited with error code $($process.ExitCode)" } } "#; pub fn get_script() -> &'static str { PS_FUNC } /// return the path to the link to the function script fn get_link_path() -> PathBuf { conf::dir().join("launcher").join(NAME).join("br.ps1") } /// return the path to the script containing the function. /// /// In XDG_DATA_HOME (typically ~/.local/share on linux) fn get_script_path() -> PathBuf { conf::app_dirs() .data_dir() .join("launcher") .join(NAME) .join(VERSION) } /// Check whether the shell function is installed, install /// it if it wasn't refused before or if broot is launched /// with --install. #[allow(unreachable_code, unused_variables)] pub fn install(si: &mut ShellInstall) -> Result<(), ShellInstallError> { info!("install {NAME}"); #[cfg(unix)] { debug!("Shell install not supported for PowerShell on unix-based systems."); return Ok(()); } let Some(user_dir) = UserDirs::new() else { warn!("Could not find user directory."); return Ok(()); }; let Some(document_dir) = user_dir.document_dir() else { warn!("Could not find user documents directory."); return Ok(()); }; let script_path = get_script_path(); si.write_script(&script_path, PS_FUNC)?; let link_path = get_link_path(); si.create_link(&link_path, &script_path)?; let escaped_path = link_path.to_string_lossy().replace(' ', "\\ "); let source_line = format!(". {}", &escaped_path); let sourcing_path = document_dir.join("WindowsPowerShell").join("Profile.ps1"); if !sourcing_path.exists() { debug!("Creating missing PowerShell profile file."); if let Some(parent) = sourcing_path.parent() { fs::create_dir_all(parent).context(&|| format!("creating {parent:?} directory"))?; } fs::File::create(&sourcing_path).context(&|| format!("creating {sourcing_path:?}"))?; } let sourcing_path_str = sourcing_path.to_string_lossy(); if util::file_contains_line(&sourcing_path, &source_line)? { mad_print_inline!( &si.skin, "`$0` already patched, no change made.\n", &sourcing_path_str, ); } else { util::append_to_file(&sourcing_path, format!("\n{source_line}\n"))?; mad_print_inline!(&si.skin, "`$0` successfully patched.\n", &sourcing_path_str,); } si.done = true; Ok(()) } broot-1.46.3/src/shell_install/state.rs000064400000000000000000000072251046102023000161640ustar 00000000000000use { super::ShellInstall, crate::{ cli, conf, errors::*, }, std::{ fs, path::PathBuf, }, }; /// must be incremented when the architecture changes or one of the shell /// specific scripts is upgraded to a new version const CURRENT_VERSION: usize = 4; const REFUSED_FILE_CONTENT: &str = r" This file tells broot you refused the installation of the companion shell function. If you want to install it run broot -- install "; const INSTALLED_FILE_CONTENT: &str = r" This file tells broot the installation of the br function was done. If there's a problem and you want to install it again run broot -- install "; #[derive(Debug, Clone, Copy, clap::ValueEnum)] pub enum ShellInstallState { NotInstalled, // before any install, this is the initial state Refused, // user doesn't want anything to be installed Obsolete, UpToDate, } impl From for ShellInstallState { fn from(cs: cli::CliShellInstallState) -> Self { match cs { cli::CliShellInstallState::Undefined => Self::NotInstalled, cli::CliShellInstallState::Refused => Self::Refused, cli::CliShellInstallState::Installed => Self::UpToDate, } } } impl ShellInstallState { pub fn get_refused_path() -> PathBuf { conf::dir().join("launcher").join("refused") } pub fn get_installed_path(version: usize) -> PathBuf { conf::dir().join("launcher").join(format!("installed-v{version}")) } pub fn detect() -> Self { let current = Self::get_installed_path(CURRENT_VERSION); if current.exists() { return Self::UpToDate; } if Self::get_refused_path().exists() { return Self::Refused; } for version in 0..CURRENT_VERSION { let installed = Self::get_installed_path(version); if installed.exists() { return Self::Obsolete; } } Self::NotInstalled } pub fn remove(si: &ShellInstall) -> Result<(), ShellInstallError> { si.remove(&Self::get_refused_path())?; for version in 0..=CURRENT_VERSION { let installed = Self::get_installed_path(version); si.remove(&installed)?; } Ok(()) } /// write either the "installed" or the "refused" file, or remove /// those files. /// /// This is useful in installation /// or test scripts when we don't want the user to be prompted /// to install the function, or in case something doesn't properly /// work in shell detections pub fn write(self, si: &ShellInstall) -> Result<(), ShellInstallError> { Self::remove(si)?; match self { ShellInstallState::Refused => { let refused_path = Self::get_refused_path(); fs::create_dir_all(refused_path.parent().unwrap()) .context(&|| format!("creating parents of {refused_path:?}"))?; fs::write(&refused_path, REFUSED_FILE_CONTENT) .context(&|| format!("writing in {refused_path:?}"))?; } ShellInstallState::UpToDate => { let installed_path = Self::get_installed_path(CURRENT_VERSION); fs::create_dir_all(installed_path.parent().unwrap()) .context(&|| format!("creating parents of {installed_path:?}"))?; fs::write(&installed_path, INSTALLED_FILE_CONTENT) .context(&|| format!("writing in {installed_path:?}"))?; } _ => { warn!("not writing state {self:?}"); } } Ok(()) } } broot-1.46.3/src/shell_install/util.rs000064400000000000000000000016151046102023000160160ustar 00000000000000use { crate::errors::*, std::{ fs::{self, OpenOptions}, io::{BufRead, BufReader, Write}, path::Path, }, }; pub fn file_contains_line(path: &Path, searched_line: &str) -> Result { let file = fs::File::open(path) .context(&|| format!("opening {path:?}"))?; for line in BufReader::new(file).lines() { let line = line.context(&|| format!("reading line in {path:?}"))?; if line == searched_line { return Ok(true); } } Ok(false) } pub fn append_to_file>(path: &Path, content: S) -> Result<(), ShellInstallError> { let mut shellrc = OpenOptions::new() .append(true) .open(path) .context(&|| format!("opening {path:?} for append"))?; shellrc.write_all(content.as_ref().as_bytes()) .context(&|| format!("writing in {path:?}"))?; Ok(()) } broot-1.46.3/src/skin/app_skin.rs000064400000000000000000000020271046102023000147520ustar 00000000000000use { super::*, crate::{ conf::Conf, }, rustc_hash::FxHashMap, }; /// all the skin things used by the broot application /// during running pub struct AppSkin { /// the skin used in the focused panel pub focused: PanelSkin, /// the skin used in unfocused panels pub unfocused: PanelSkin, } impl AppSkin { pub fn new(conf: &Conf, no_style: bool) -> Self { if no_style { Self { focused: PanelSkin::new(StyleMap::no_term()), unfocused: PanelSkin::new(StyleMap::no_term()), } } else { let def_skin; let skin = if let Some(skin) = &conf.skin { skin } else { def_skin = FxHashMap::default(); &def_skin }; let StyleMaps { focused, unfocused } = StyleMaps::create(skin); Self { focused: PanelSkin::new(focused), unfocused: PanelSkin::new(unfocused), } } } } broot-1.46.3/src/skin/cli_mad_skin.rs000064400000000000000000000007461046102023000155700ustar 00000000000000use { crokey::crossterm::style::Color, termimad::{gray, MadSkin}, }; /// build a termimad skin for cli output (mostly /// for the install process) pub fn make_cli_mad_skin() -> MadSkin { let mut skin = MadSkin::default(); skin.set_headers_fg(Color::AnsiValue(178)); skin.inline_code.set_bg(gray(2)); skin.inline_code.set_fg(gray(18)); skin.code_block.set_bg(gray(2)); skin.code_block.set_fg(gray(18)); skin.italic.set_fg(Color::Magenta); skin } broot-1.46.3/src/skin/ext_colors.rs000064400000000000000000000022441046102023000153300ustar 00000000000000use { crate::{ errors::InvalidSkinError, }, rustc_hash::FxHashMap, crokey::crossterm::style::Color, lazy_regex::*, std::convert::TryFrom, termimad::parse_color, }; /// a map from file extension to the foreground /// color to use when drawing the tree #[derive(Debug, Clone, Default)] pub struct ExtColorMap { map: FxHashMap, } impl ExtColorMap { /// return the color to use, or None when the default color /// of files should apply pub fn get(&self, ext: &str) -> Option { self.map.get(ext).copied() } pub fn set(&mut self, ext: String, raw_color: &str) -> Result<(), InvalidSkinError> { if !regex_is_match!("^none$"i, raw_color) { let color = parse_color(raw_color)?; self.map.insert(ext, color); } Ok(()) } } impl TryFrom<&FxHashMap> for ExtColorMap { type Error = InvalidSkinError; fn try_from(raw_map: &FxHashMap) -> Result { let mut map = ExtColorMap::default(); for (k, v) in raw_map { map.set(k.to_string(), v)?; } Ok(map) } } broot-1.46.3/src/skin/help_mad_skin.rs000064400000000000000000000021221046102023000157370ustar 00000000000000use { super::StyleMap, termimad::{Alignment, LineStyle, MadSkin}, }; /// build a MadSkin, which will be used for markdown formatting /// for the help screen by applying the `help_*` entries /// of the skin. pub fn make_help_mad_skin(skin: &StyleMap) -> MadSkin { let mut ms = MadSkin::default(); ms.paragraph.compound_style = skin.help_paragraph.clone(); ms.inline_code = skin.help_code.clone(); ms.code_block.compound_style = ms.inline_code.clone(); ms.bold = skin.help_bold.clone(); ms.italic = skin.help_italic.clone(); ms.table = LineStyle::new( skin.help_table_border.clone(), Alignment::Center, ); if let Some(c) = skin.help_headers.get_fg() { ms.set_headers_fg(c); } if let Some(c) = skin.help_headers.get_bg() { ms.set_headers_bg(c); } ms.bullet .set_compound_style(ms.paragraph.compound_style.clone()); ms.scrollbar .track .set_compound_style(skin.scrollbar_track.clone()); ms.scrollbar .thumb .set_compound_style(skin.scrollbar_thumb.clone()); ms } broot-1.46.3/src/skin/mod.rs000064400000000000000000000015431046102023000137270ustar 00000000000000mod app_skin; mod cli_mad_skin; mod ext_colors; mod help_mad_skin; mod panel_skin; mod purpose_mad_skin; mod skin_entry; mod style_map; mod status_mad_skin; pub use { app_skin::AppSkin, cli_mad_skin::*, ext_colors::ExtColorMap, help_mad_skin::*, panel_skin::PanelSkin, purpose_mad_skin::*, skin_entry::SkinEntry, style_map::{StyleMap, StyleMaps}, status_mad_skin::StatusMadSkinSet, }; use crokey::crossterm::style::Color::{self, *}; pub fn gray(mut level: u8) -> Option { if level > 23 { // this only happens when I mess the literals in style_map.rs warn!("fixed invalid gray level: {}", level); level = 23 } Some(AnsiValue(0xE8 + level)) } pub fn rgb(r: u8, g: u8, b: u8) -> Option { Some(Rgb { r, g, b }) } pub fn ansi(v: u8) -> Option { Some(AnsiValue(v)) } broot-1.46.3/src/skin/panel_skin.rs000064400000000000000000000013761046102023000152770ustar 00000000000000use { super::*, termimad::MadSkin, }; /// the various skin things used in a panel. /// /// There are normally two instances of this struct in /// a broot application: one is used for the focused panel /// and one is used for the other panels. pub struct PanelSkin { pub styles: StyleMap, pub purpose_skin: MadSkin, pub status_skin: StatusMadSkinSet, pub help_skin: MadSkin, } impl PanelSkin { pub fn new(styles: StyleMap) -> Self { let purpose_skin = make_purpose_mad_skin(&styles); let status_skin = StatusMadSkinSet::from_skin(&styles); let help_skin = make_help_mad_skin(&styles); Self { styles, purpose_skin, status_skin, help_skin, } } } broot-1.46.3/src/skin/purpose_mad_skin.rs000064400000000000000000000010201046102023000165000ustar 00000000000000 use { super::StyleMap, termimad::{Alignment, LineStyle, MadSkin}, }; /// build a MadSkin which will be used to display the status /// when there's no error pub fn make_purpose_mad_skin(skin: &StyleMap) -> MadSkin { MadSkin { paragraph: LineStyle::new( skin.purpose_normal.clone(), Alignment::Left, ), italic: skin.purpose_italic.clone(), bold: skin.purpose_bold.clone(), ellipsis: skin.purpose_ellipsis.clone(), ..Default::default() } } broot-1.46.3/src/skin/skin_entry.rs000064400000000000000000000034741046102023000153420ustar 00000000000000//! Manage conversion of a user provided string //! defining foreground and background colors into //! a string with TTY colors use { crate::errors::InvalidSkinError, serde::{de::Error, Deserialize, Deserializer}, termimad::{ CompoundStyle, parse_compound_style, }, }; /// Parsed content of a [skin] line of the conf.toml file #[derive(Clone, Debug)] pub struct SkinEntry { focused: CompoundStyle, unfocused: Option, } impl SkinEntry { pub fn new(focused: CompoundStyle, unfocused: Option) -> Self { Self { focused, unfocused } } pub fn get_focused(&self) -> &CompoundStyle { &self.focused } pub fn get_unfocused(&self) -> &CompoundStyle { self.unfocused.as_ref().unwrap_or(&self.focused) } /// Parse a string representation of a skin entry. /// /// The general form is either "" or " / ": /// It may be just the focused compound_style, or both /// the focused and the unfocused ones, in which case there's /// a '/' as separator. /// /// Each part is " " /// where the attributes list may be empty. pub fn parse(s: &str) -> Result { let mut parts = s.split('/'); let focused = parse_compound_style(parts.next().unwrap())?; let unfocused = parts.next() .map(parse_compound_style) .transpose()?; Ok(Self { focused, unfocused }) } } impl<'de> Deserialize<'de> for SkinEntry { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de> { let s = String::deserialize(deserializer)?; SkinEntry::parse(&s) .map_err(|e| D::Error::custom(e.to_string())) } } broot-1.46.3/src/skin/status_mad_skin.rs000064400000000000000000000024371046102023000163430ustar 00000000000000use { super::StyleMap, termimad::{Alignment, LineStyle, MadSkin}, }; /// the mad skin applying to the status depending whether it's an /// error or not pub struct StatusMadSkinSet { pub normal: MadSkin, pub error: MadSkin, } /// build a MadSkin which will be used to display the status /// when there's no error fn make_normal_status_mad_skin(skin: &StyleMap) -> MadSkin { MadSkin { paragraph: LineStyle::new( skin.status_normal.clone(), Alignment::Left, ), italic: skin.status_italic.clone(), bold: skin.status_bold.clone(), inline_code: skin.status_code.clone(), ellipsis: skin.status_ellipsis.clone(), ..Default::default() } } /// build a MadSkin which will be used to display the status /// when there's a error fn make_error_status_mad_skin(skin: &StyleMap) -> MadSkin { MadSkin { paragraph: LineStyle::new( skin.status_error.clone(), Alignment::Left, ), ellipsis: skin.status_ellipsis.clone(), ..Default::default() } } impl StatusMadSkinSet { pub fn from_skin(skin: &StyleMap) -> Self { Self { normal: make_normal_status_mad_skin(skin), error: make_error_status_mad_skin(skin), } } } broot-1.46.3/src/skin/style_map.rs000064400000000000000000000212211046102023000151400ustar 00000000000000/// Defines the StyleMap structure with its default value. /// /// A style_map is a collection of termimad compound_style. It's /// either defined for the focused panel state or the unfocused /// one (there are thus two instances in the application) use { super::*, crate::{ errors::ProgramError, }, crokey::crossterm::{ style::{ Attribute::*, Attributes, Color::*, SetBackgroundColor, }, QueueableCommand, }, rustc_hash::FxHashMap, std::{ fmt, io::Write, }, termimad::CompoundStyle, }; // this macro, which must be called once, creates // the StyleMap struct with its creation functions handling // both default values defined in the macro call and // overriding values defined in TOML macro_rules! StyleMap { ( $( $name:ident: $fg:expr, $bg:expr, [$($attr:expr)*] $( / $fgu:expr, $bgu:expr , [$($attru:expr)*] )* )* ) => { /// a struct whose fields are /// - a boolean telling whether it's a no-style map /// - the styles to apply to various parts/cases pub struct StyleMap { styled: bool, $(pub $name: CompoundStyle,)* } /// a set of two style_maps: one for the focused panel and one for the other panels /// /// This struct is just a vessel for the skin initialization process. pub struct StyleMaps { pub focused: StyleMap, pub unfocused: StyleMap, } impl StyleMap { /// build a skin without any terminal control character (for file output) pub fn no_term() -> Self { Self { styled: false, $($name: CompoundStyle::default(),)* } } /// ensures the "default" skin entry is used as base for all other /// entries (this processus is part of the skin initialization) fn diffuse_default(&mut self) { $( let mut base = self.default.clone(); base.overwrite_with(&self.$name); self.$name = base; )* } } impl StyleMaps { pub fn create(skin_conf: &FxHashMap) -> Self { let mut focused = StyleMap { styled: true, $($name: skin_conf .get(stringify!($name)) .map(|sec| sec.get_focused().clone()) .unwrap_or( CompoundStyle::new( $fg, $bg, Attributes::from(vec![$($attr),*].as_slice()), ) ) ,)* }; focused.diffuse_default(); let mut unfocused = StyleMap { styled: true, $($name: CompoundStyle::default(),)* }; $( unfocused.$name = CompoundStyle::new( $fg, $bg, Attributes::from(vec![$($attr),*].as_slice()), ); $( unfocused.$name = CompoundStyle::new( $fgu, $bgu, Attributes::from(vec![$($attru),*].as_slice()), ); )* if let Some(sec) = skin_conf.get(stringify!($name)) { unfocused.$name = sec.get_unfocused().clone(); } )* unfocused.diffuse_default(); Self { focused, unfocused, } } } impl Clone for StyleMap { fn clone(&self) -> Self { Self { styled: self.styled, $($name: self.$name.clone(),)* } } } } } impl StyleMap { pub fn queue_reset(&self, f: &mut W) -> Result<(), ProgramError> { if self.styled { f.queue(SetBackgroundColor(Color::Reset))?; } Ok(()) } pub fn good_to_bad_color(&self, value: f64) -> Color { debug_assert!((0.0..=1.0).contains(&value)); const N: usize = 10; let idx = (value * N as f64) as usize; let cs = match idx { 0 => &self.good_to_bad_0, 1 => &self.good_to_bad_1, 2 => &self.good_to_bad_2, 3 => &self.good_to_bad_3, 4 => &self.good_to_bad_4, 5 => &self.good_to_bad_5, 6 => &self.good_to_bad_6, 7 => &self.good_to_bad_7, 8 => &self.good_to_bad_8, _ => &self.good_to_bad_9, }; cs.object_style.foreground_color.unwrap_or(Color::Blue) } } // Default styles defined as // name: forecolor, backcolor, [attributes] // The optional part after a '/' is the style for unfocused panels // (if missing the style is the same than for focused panels) StyleMap! { default: gray(22), gray(2), [] / gray(20), gray(2), [] tree: gray(8), None, [] / gray(4), None, [] parent: gray(18), None, [] / gray(13), None, [] file: gray(22), None, [] / gray(15), None, [] directory: ansi(110), None, [Bold] / ansi(110), None, [] exe: Some(Cyan), None, [] link: Some(Magenta), None, [] pruning: gray(12), None, [Italic] perm__: gray(5), None, [] perm_r: ansi(94), None, [] perm_w: ansi(132), None, [] perm_x: ansi(65), None, [] owner: ansi(138), None, [] group: ansi(131), None, [] count: ansi(138), gray(4), [] dates: ansi(66), None, [] sparse: ansi(214), None, [] content_extract: ansi(29), None, [] content_match: ansi(34), None, [] device_id_major: ansi(138), None, [] device_id_sep: ansi(102), None, [] device_id_minor: ansi(138), None, [] git_branch: ansi(178), None, [] git_insertions: ansi(28), None, [] git_deletions: ansi(160), None, [] git_status_current: gray(5), None, [] git_status_modified: ansi(28), None, [] git_status_new: ansi(94), None, [Bold] git_status_ignored: gray(17), None, [] git_status_conflicted: ansi(88), None, [] git_status_other: ansi(88), None, [] selected_line: None, gray(6), [] / None, gray(4), [] char_match: Some(Green), None, [] file_error: Some(Red), None, [] flag_label: gray(15), gray(2), [] flag_value: ansi(178), gray(2), [Bold] input: Some(White), gray(2), [] / gray(15), None, [] status_error: gray(22), ansi(124), [] status_job: ansi(220), gray(5), [] status_normal: gray(20), gray(4), [] / gray(2), gray(2), [] status_italic: ansi(178), gray(4), [] / gray(2), gray(2), [] status_bold: ansi(178), gray(4), [Bold] / gray(2), gray(2), [] status_code: ansi(229), gray(4), [] / gray(2), gray(2), [] status_ellipsis: gray(19), gray(1), [] / gray(2), gray(2), [] purpose_normal: gray(20), gray(2), [] purpose_italic: ansi(178), gray(2), [] purpose_bold: ansi(178), gray(2), [Bold] purpose_ellipsis: gray(20), gray(2), [] scrollbar_track: gray(7), None, [] / gray(4), None, [] scrollbar_thumb: gray(22), None, [] / gray(14), None, [] help_paragraph: gray(20), None, [] help_bold: ansi(178), None, [Bold] help_italic: ansi(229), None, [] help_code: gray(21), gray(3), [] help_headers: ansi(178), None, [] help_table_border: ansi(239), None, [] preview: gray(20), gray(1), [] / gray(18), gray(2), [] preview_title: gray(23), gray(2), [] / gray(21), gray(2), [] preview_line_number: gray(12), gray(3), [] preview_separator: gray(7), None, [] preview_match: None, ansi(29), [] hex_null: gray(8), None, [] hex_ascii_graphic: gray(18), None, [] hex_ascii_whitespace: ansi(143), None, [] hex_ascii_other: ansi(215), None, [] hex_non_ascii: ansi(167), None, [] staging_area_title: gray(22), gray(2), [] / gray(20), gray(3), [] mode_command_mark: gray(5), ansi(204), [Bold] good_to_bad_0: ansi(28), None, [] good_to_bad_1: ansi(29), None, [] good_to_bad_2: ansi(29), None, [] good_to_bad_3: ansi(29), None, [] good_to_bad_4: ansi(29), None, [] good_to_bad_5: ansi(100), None, [] good_to_bad_6: ansi(136), None, [] good_to_bad_7: ansi(172), None, [] good_to_bad_8: ansi(166), None, [] good_to_bad_9: ansi(196), None, [] } impl fmt::Debug for StyleMap { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Skin") } } broot-1.46.3/src/stage/filtered_stage.rs000064400000000000000000000133321046102023000162670ustar 00000000000000use { super::*, crate::{ pattern::*, }, std::{ convert::TryFrom, path::{Path, PathBuf}, }, }; #[derive(Clone)] pub struct FilteredStage { stage_version: usize, paths_idx: Vec, // indexes of the matching paths in the stage pattern: InputPattern, // an optional filtering pattern selection: Option, // index in paths_idx, always in [0, paths_idx.len()[ } impl FilteredStage { pub fn unfiltered(stage: &Stage) -> Self { Self::filtered(stage, InputPattern::none()) } /// compute the paths_idx and maybe change the selection fn compute(&mut self, stage: &Stage) { if self.pattern.is_none() { self.paths_idx = stage.paths().iter() .enumerate() .map(|(idx, _)| idx) .collect(); } else { let mut best_score = None; self.paths_idx.clear(); for (idx, path) in stage.paths().iter().enumerate() { if let Some(file_name) = path.file_name() { let subpath = path.to_string_lossy().to_string(); let name = file_name.to_string_lossy().to_string(); let regular_file = path.is_file(); let candidate = Candidate { path, subpath: &subpath, name: &name, regular_file, }; if let Some(score) = self.pattern.pattern.score_of(candidate) { let is_best = match best_score { Some(old_score) if old_score < score => true, None => true, _ => false, }; if is_best { self.selection = Some(self.paths_idx.len()); best_score = Some(score); } self.paths_idx.push(idx); } } } } } pub fn filtered(stage: &Stage, pattern: InputPattern) -> Self { let mut fs = Self { stage_version: stage.version(), paths_idx: Vec::new(), pattern, selection: None, }; fs.compute(stage); fs } /// check whether the stage has changed, and update the /// filtered list if necessary pub fn update(&mut self, stage: &Stage) -> bool { if stage.version() == self.stage_version { false } else { self.compute(stage); true } } /// change the pattern, keeping the selection if possible /// Assumes the stage didn't change (if it changed, we lose the /// selection) pub fn set_pattern(&mut self, stage: &Stage, pattern: InputPattern) { self.stage_version = stage.version(); // in case it changed self.pattern = pattern; self.compute(stage); } pub fn len(&self) -> usize { self.paths_idx.len() } pub fn path<'s>(&self, stage: &'s Stage, idx: usize) -> Option<&'s Path> { self.paths_idx .get(idx) .and_then(|&idx| stage.paths().get(idx)) .map(PathBuf::as_path) } pub fn path_sel<'s>(&self, stage: &'s Stage, idx: usize) -> Option<(&'s Path, bool)> { self.path(stage, idx) .map(|p| (p, self.selection.map_or(false, |si| idx==si))) } pub fn pattern(&self) -> &InputPattern { &self.pattern } pub fn selection(&self) -> Option { self.selection } pub fn has_selection(&self) -> bool { self.selection.is_some() } pub fn try_select_idx(&mut self, idx: usize) -> bool { if idx < self.paths_idx.len() { self.selection = Some(idx); true } else { false } } pub fn selected_path<'s>(&self, stage: &'s Stage) -> Option<&'s Path> { self.selection .and_then(|pi| self.paths_idx.get(pi)) .and_then(|&idx| stage.paths().get(idx)) .map(|p| p.as_path()) } pub fn unselect(&mut self) { self.selection = None } /// unstage the selection, if any, or return false. /// If possible we select the item below so that the user /// may easily remove a few items pub fn unstage_selection(&mut self, stage: &mut Stage) -> bool { if let Some(spi) = self.selection { stage.remove_idx(self.paths_idx[spi]); self.stage_version = stage.version(); self.compute(stage); if spi >= self.paths_idx.len() { self.selection = Some(spi); }; true } else { false } } pub fn move_selection(&mut self, dy: i32, cycle: bool) { self.selection = if self.paths_idx.is_empty() { None } else if let Some(sel_idx) = self.selection.and_then(|i| i32::try_from(i).ok()) { let new_sel_idx = sel_idx + dy; Some( if new_sel_idx < 0 { if cycle && sel_idx == 0 { self.paths_idx.len() - 1 } else { 0 } } else if new_sel_idx as usize >= self.paths_idx.len() { if cycle && sel_idx == self.paths_idx.len() as i32 - 1 { 0 } else { self.paths_idx.len() - 1 } } else { new_sel_idx as usize } ) } else if dy < 0 { Some(self.paths_idx.len() - 1) } else { Some(0) }; } } broot-1.46.3/src/stage/mod.rs000064400000000000000000000002301046102023000140560ustar 00000000000000mod filtered_stage; mod stage; mod stage_state; mod stage_sum; pub use { filtered_stage::*, stage::*, stage_state::*, stage_sum::*, }; broot-1.46.3/src/stage/stage.rs000064400000000000000000000047561046102023000144230ustar 00000000000000use { crate::{ app::AppContext, file_sum::FileSum, task_sync::Dam, }, std::{ path::{Path, PathBuf}, }, }; /// a staging area: selection of several paths /// for later user /// /// The structure is versioned to allow caching /// of derived structs (filtered list mainly). This /// scheme implies the stage isn't cloned, and that /// it exists in only one instance #[derive(Default, Debug)] pub struct Stage { version: usize, paths: Vec, } impl Stage { pub fn contains(&self, path: &Path) -> bool { self.paths .iter() .any(|p| p==path) } pub fn is_empty(&self) -> bool { self.paths.is_empty() } /// return true when there's a change pub fn add(&mut self, path: PathBuf) -> bool { if self.contains(&path) { false } else { self.version += 1; self.paths.push(path); true } } /// return true when there's a change pub fn remove(&mut self, path: &Path) -> bool { if let Some(pos) = self.paths.iter().position(|p| p == path) { self.version += 1; self.paths.remove(pos); true } else { false } } pub fn remove_idx(&mut self, idx: usize) { if idx < self.paths.len() { self.version += 1; self.paths.remove(idx); } } pub fn clear(&mut self) { self.version += 1; self.paths.clear(); } pub fn paths(&self) -> &[PathBuf] { &self.paths } /// removes paths to non existing files pub fn refresh(&mut self) { let len_before = self.paths.len(); self.paths.retain(|p| p.exists()); if self.paths.len() != len_before { self.version += 1; } } pub fn len(&self) -> usize { self.paths.len() } pub fn version(&self) -> usize { self.version } pub fn compute_sum(&self, dam: &Dam, con: &AppContext) -> Option { let mut sum = FileSum::zero(); for path in &self.paths { if path.is_dir() { let dir_sum = FileSum::from_dir(path, dam, con); if let Some(dir_sum) = dir_sum { sum += dir_sum; } else { return None; // computation was interrupted } } else { sum += FileSum::from_file(path); } } Some(sum) } } broot-1.46.3/src/stage/stage_state.rs000064400000000000000000000437541046102023000156240ustar 00000000000000use { super::*, crate::{ app::*, command::*, display::{MatchedString, Screen, W}, errors::ProgramError, pattern::*, skin::*, task_sync::Dam, tree::*, verb::*, }, crokey::crossterm::{ cursor, QueueableCommand, }, std::path::{Path}, termimad::{Area, CropWriter, SPACE_FILLING}, unicode_width::{UnicodeWidthChar, UnicodeWidthStr}, }; static TITLE: &str = "Staging Area"; // no wide char allowed here static COUNT_LABEL: &str = " count: "; static SIZE_LABEL: &str = " size: "; static ELLIPSIS: char = '…'; pub struct StageState { filtered_stage: FilteredStage, scroll: usize, tree_options: TreeOptions, /// the 'modal' mode mode: Mode, page_height: usize, stage_sum: StageSum, } impl StageState { pub fn new( app_state: &AppState, tree_options: TreeOptions, con: &AppContext, ) -> StageState { let filtered_stage = FilteredStage::filtered( &app_state.stage, tree_options.pattern.clone(), ); Self { filtered_stage, scroll: 0, tree_options, mode: con.initial_mode(), page_height: 0, stage_sum: StageSum::default(), } } fn need_sum_computation(&self) -> bool { self.tree_options.show_sizes && !self.stage_sum.is_up_to_date() } pub fn try_scroll( &mut self, cmd: ScrollCommand, ) -> bool { let old_scroll = self.scroll; self.scroll = cmd.apply(self.scroll, self.filtered_stage.len(), self.page_height); self.scroll != old_scroll } pub fn fix_scroll(&mut self) { let len = self.filtered_stage.len(); if self.scroll + self.page_height > len { self.scroll = if len > self.page_height { len - self.page_height } else { 0 }; } } fn write_title_line( &self, stage: &Stage, cw: &mut CropWriter<'_, W>, styles: &StyleMap, ) -> Result<(), ProgramError> { let total_count = format!("{}", stage.len()); let mut count_len = total_count.len(); if self.filtered_stage.pattern().is_some() { count_len += total_count.len() + 1; // 1 for '/' } if cw.allowed < count_len { return Ok(()); } if TITLE.len() + 1 + count_len <= cw.allowed { cw.queue_str( &styles.staging_area_title, TITLE, )?; } let mut show_count_label = false; let mut rem = cw.allowed - count_len; if COUNT_LABEL.len() < rem { rem -= COUNT_LABEL.len(); show_count_label = true; if self.tree_options.show_sizes { if let Some(sum) = self.stage_sum.computed() { let size = file_size::fit_4(sum.to_size()); let size_len = SIZE_LABEL.len() + size.len(); if size_len < rem { rem -= size_len; // we display the size in the middle, so we cut rem in two let left_rem = rem / 2; rem -= left_rem; cw.repeat(&styles.staging_area_title, &SPACE_FILLING, left_rem)?; cw.queue_g_string( &styles.staging_area_title, SIZE_LABEL.to_string(), )?; cw.queue_g_string( &styles.staging_area_title, size, )?; } } } } cw.repeat(&styles.staging_area_title, &SPACE_FILLING, rem)?; if show_count_label { cw.queue_g_string( &styles.staging_area_title, COUNT_LABEL.to_string(), )?; } if self.filtered_stage.pattern().is_some() { cw.queue_g_string( &styles.char_match, format!("{}", self.filtered_stage.len()), )?; cw.queue_char( &styles.staging_area_title, '/', )?; } cw.queue_g_string( &styles.staging_area_title, total_count, )?; cw.fill(&styles.staging_area_title, &SPACE_FILLING)?; Ok(()) } fn move_selection(&mut self, dy: i32, cycle: bool) -> CmdResult { self.filtered_stage.move_selection(dy, cycle); if let Some(sel) = self.filtered_stage.selection() { if sel < self.scroll + 5 { self.scroll = (sel as i32 -5).max(0) as usize; } else if sel > self.scroll + self.page_height - 5 { self.scroll = (sel + 5 - self.page_height) .min(self.filtered_stage.len() - self.page_height); } } CmdResult::Keep } } impl PanelState for StageState { fn get_type(&self) -> PanelStateType { PanelStateType::Stage } fn selected_path(&self) -> Option<&Path> { None } fn selection(&self) -> Option> { None } fn clear_pending(&mut self) { self.stage_sum.clear(); } fn do_pending_task( &mut self, app_state: &mut AppState, _screen: Screen, con: &AppContext, dam: &mut Dam, // need the stage here ) -> Result<(), ProgramError> { if self.need_sum_computation() { self.stage_sum.compute(&app_state.stage, dam, con); } Ok(()) } fn get_pending_task(&self) -> Option<&'static str> { if self.need_sum_computation() { Some("stage size summing") } else { None } } fn sel_info<'c>(&'c self, app_state: &'c AppState) -> SelInfo<'c> { match app_state.stage.len() { 0 => SelInfo::None, 1 => SelInfo::One(Selection { path: &app_state.stage.paths()[0], stype: SelectionType::File, is_exe: false, line: 0, }), _ => SelInfo::More(&app_state.stage), } } fn has_at_least_one_selection(&self, app_state: &AppState) -> bool { !app_state.stage.is_empty() } fn tree_options(&self) -> TreeOptions { self.tree_options.clone() } /// option changing is unlikely to be done on this state, but /// we'll still do it in case a future scenario makes it possible /// to open a different state from this state fn with_new_options( &mut self, _screen: Screen, change_options: &dyn Fn(&mut TreeOptions) -> &'static str, in_new_panel: bool, con: &AppContext, ) -> CmdResult { if in_new_panel { CmdResult::error("stage can't be displayed in two panels") } else { let mut new_options= self.tree_options(); let message = change_options(&mut new_options); let state = Box::new(StageState { filtered_stage: self.filtered_stage.clone(), scroll: self.scroll, mode: con.initial_mode(), tree_options: new_options, page_height: self.page_height, stage_sum: self.stage_sum, }); CmdResult::NewState { state, message: Some(message) } } } fn on_click( &mut self, _x: u16, y: u16, _screen: Screen, _con: &AppContext, ) -> Result { if y > 0 { // the list starts on the second row self.filtered_stage.try_select_idx(y as usize - 1 + self.scroll); } Ok(CmdResult::Keep) } fn on_pattern( &mut self, pat: InputPattern, app_state: &AppState, _con: &AppContext, ) -> Result { self.filtered_stage.set_pattern(&app_state.stage, pat); self.fix_scroll(); Ok(CmdResult::Keep) } fn display( &mut self, w: &mut W, disc: &DisplayContext, ) -> Result<(), ProgramError> { let stage = &disc.app_state.stage; self.stage_sum.see_stage(stage); // this may invalidate the sum if self.filtered_stage.update(stage) { self.fix_scroll(); } let area = &disc.state_area; let styles = &disc.panel_skin.styles; let width = area.width as usize; w.queue(cursor::MoveTo(area.left, 0))?; let mut cw = CropWriter::new(w, width); self.write_title_line(stage, &mut cw, styles)?; let list_area = Area::new(area.left, area.top + 1, area.width, area.height - 1); self.page_height = list_area.height as usize; let pattern = &self.filtered_stage.pattern().pattern; let pattern_object = pattern.object(); let scrollbar = list_area.scrollbar(self.scroll, self.filtered_stage.len()); for idx in 0..self.page_height { let y = list_area.top + idx as u16; let stage_idx = idx + self.scroll; w.queue(cursor::MoveTo(area.left, y))?; let mut cw = CropWriter::new(w, width - 1); let cw = &mut cw; if let Some((path, selected)) = self.filtered_stage.path_sel(stage, stage_idx) { let mut style = if path.is_dir() { &styles.directory } else { &styles.file }; let mut bg_style; if selected { bg_style = style.clone(); if let Some(c) = styles.selected_line.get_bg() { bg_style.set_bg(c); } style = &bg_style; } let mut bg_style_match; let mut style_match = &styles.char_match; if selected { bg_style_match = style_match.clone(); if let Some(c) = styles.selected_line.get_bg() { bg_style_match.set_bg(c); } style_match = &bg_style_match; } if disc.con.show_selection_mark && self.filtered_stage.has_selection() { cw.queue_char(style, if selected { '▶' } else { ' ' })?; } if pattern_object.subpath { let label = path.to_string_lossy(); // we must display the matching on the whole path // (subpath is the path for the staging area) let name_match = pattern.search_string(&label); let matched_string = MatchedString::new( name_match, &label, style, style_match, ); matched_string.queue_on(cw)?; } else if let Some(file_name) = path.file_name() { let label = file_name.to_string_lossy(); let label_cols = label.width(); if label_cols + 2 < cw.allowed { if let Some(parent_path) = path.parent() { let mut parent_style = &styles.parent; let mut bg_style; if selected { bg_style = parent_style.clone(); if let Some(c) = styles.selected_line.get_bg() { bg_style.set_bg(c); } parent_style = &bg_style; } let cols_max = cw.allowed - label_cols - 3; let parent_path = parent_path.to_string_lossy(); let parent_cols = parent_path.width(); if parent_cols <= cols_max { cw.queue_str( parent_style, &parent_path, )?; } else { // TODO move to (crop_writer ? termimad ?) // we'll compute the size of the tail fitting // the width minus one (for the ellipsis) let mut bytes_count = 0; let mut cols_count = 0; for c in parent_path.chars().rev() { let char_width = UnicodeWidthChar::width(c).unwrap_or(0); let next_str_width = cols_count + char_width; if next_str_width > cols_max { break; } cols_count = next_str_width; bytes_count += c.len_utf8(); } cw.queue_char( parent_style, ELLIPSIS, )?; cw.queue_str( parent_style, &parent_path[parent_path.len()-bytes_count..], )?; } cw.queue_char( parent_style, '/', )?; } } let name_match = pattern.search_string(&label); let matched_string = MatchedString::new( name_match, &label, style, style_match, ); matched_string.queue_on(cw)?; } else { // this should not happen warn!("how did we fall on a path without filename?"); } cw.fill(style, &SPACE_FILLING)?; } cw.fill(&styles.default, &SPACE_FILLING)?; let scrollbar_style = if ScrollCommand::is_thumb(y, scrollbar) { &styles.scrollbar_thumb } else { &styles.scrollbar_track }; scrollbar_style.queue_str(w, "▐")?; } Ok(()) } fn refresh(&mut self, _screen: Screen, _con: &AppContext) -> Command { Command::empty() } fn set_mode(&mut self, mode: Mode) { self.mode = mode; } fn get_mode(&self) -> Mode { self.mode } fn on_internal( &mut self, w: &mut W, invocation_parser: Option<&InvocationParser>, internal_exec: &InternalExecution, input_invocation: Option<&VerbInvocation>, trigger_type: TriggerType, app_state: &mut AppState, cc: &CmdContext, ) -> Result { Ok(match internal_exec.internal { Internal::back if self.filtered_stage.pattern().is_some() => { self.filtered_stage = FilteredStage::unfiltered(&app_state.stage); CmdResult::Keep } Internal::back if self.filtered_stage.has_selection() => { self.filtered_stage.unselect(); CmdResult::Keep } Internal::line_down => { let count = get_arg(input_invocation, internal_exec, 1); self.move_selection(count, true) } Internal::line_up => { let count = get_arg(input_invocation, internal_exec, 1); self.move_selection(-count, true) } Internal::line_down_no_cycle => { let count = get_arg(input_invocation, internal_exec, 1); self.move_selection(count, false) } Internal::line_up_no_cycle => { let count = get_arg(input_invocation, internal_exec, 1); self.move_selection(-count, false) } Internal::page_down => { self.try_scroll(ScrollCommand::Pages(1)); CmdResult::Keep } Internal::page_up => { self.try_scroll(ScrollCommand::Pages(-1)); CmdResult::Keep } Internal::stage => { // shall we restage what we just unstaged ? CmdResult::error("nothing to stage here") } Internal::unstage | Internal::toggle_stage => { if self.filtered_stage.unstage_selection(&mut app_state.stage) { CmdResult::Keep } else { CmdResult::error("you must select a path to unstage") } } Internal::trash => { info!("trash {} staged files", app_state.stage.len()); #[cfg(feature = "trash")] match trash::delete_all(app_state.stage.paths()) { Ok(()) => { debug!("trash success"); CmdResult::RefreshState { clear_cache: true } } Err(e) => { warn!("trash error: {:?}", &e); CmdResult::DisplayError(format!("trash error: {:?}", &e)) } } #[cfg(not(feature = "trash"))] CmdResult::DisplayError("feature not enabled or platform does not support trash".into()) } _ => self.on_internal_generic( w, invocation_parser, internal_exec, input_invocation, trigger_type, app_state, cc, )?, }) } } broot-1.46.3/src/stage/stage_sum.rs000064400000000000000000000021261046102023000152740ustar 00000000000000use { super::*, crate::{ app::AppContext, file_sum::FileSum, task_sync::Dam, }, }; #[derive(Clone, Copy, Default)] pub struct StageSum { stage_version: usize, sum: Option, } impl StageSum { /// invalidates the computed sum if the version at compilation /// time is older than the current one pub fn see_stage(&mut self, stage: &Stage) { if stage.version() != self.stage_version { self.sum = None; } } pub fn is_up_to_date(&self) -> bool { self.sum.is_some() } pub fn clear(&mut self) { self.sum = None; } pub fn compute(&mut self, stage: &Stage, dam: &Dam, con: &AppContext) -> Option { if self.stage_version != stage.version() { self.sum = None; } self.stage_version = stage.version(); if self.sum.is_none() { // produces None in case of interruption self.sum = stage.compute_sum(dam, con); } self.sum } pub fn computed(&self) -> Option { self.sum } } broot-1.46.3/src/syntactic/mod.rs000064400000000000000000000002361046102023000147620ustar 00000000000000mod syntactic_view; mod syntax_theme; mod syntaxer; pub use { syntactic_view::SyntacticView, syntaxer::{SYNTAXER, Syntaxer}, syntax_theme::*, }; broot-1.46.3/src/syntactic/syntactic_view.rs000064400000000000000000000510371046102023000172430ustar 00000000000000use { super::*, crate::{ app::{AppContext, LineNumber}, command::{ScrollCommand, move_sel}, display::{Screen, W}, errors::*, pattern::{InputPattern, NameMatch}, skin::PanelSkin, task_sync::Dam, }, crokey::crossterm::{ cursor, style::{Color, Print, SetBackgroundColor, SetForegroundColor}, QueueableCommand, }, memmap2::Mmap, once_cell::sync::Lazy, std::{ fs::File, io::{BufRead, BufReader}, path::{Path, PathBuf}, str, }, syntect::highlighting::Style, termimad::{Area, CropWriter, Filling, SPACE_FILLING}, }; pub static SEPARATOR_FILLING: Lazy = Lazy::new(|| { Filling::from_char('─') }); /// Homogeneously colored piece of a line #[derive(Debug)] pub struct Region { pub fg: Color, pub string: String, } /// when the file is bigger, we don't style it and we don't keep /// it in memory: we just keep the offsets of the lines in the /// file. const MAX_SIZE_FOR_STYLING: u64 = 2_000_000; impl Region { pub fn from_syntect(region: &(Style, &str)) -> Self { let fg = Color::Rgb { r: region.0.foreground.r, g: region.0.foreground.g, b: region.0.foreground.b, }; let string = region.1.to_string(); Self { fg, string } } } #[derive(Debug)] pub enum DisplayLine { Content(Line), Separator, } #[derive(Debug)] pub struct Line { pub number: LineNumber, // starting at 1 pub start: usize, // offset in the file, in bytes pub len: usize, // len in bytes pub regions: Vec, // not always computed pub name_match: Option, } pub struct SyntacticView { pub path: PathBuf, pub pattern: InputPattern, lines: Vec, scroll: usize, page_height: usize, selection_idx: Option, // index in lines of the selection, if any content_lines_count: usize, // number of lines excluding separators total_lines_count: usize, // including lines not filtered out } impl DisplayLine { pub fn line_number(&self) -> Option { match self { DisplayLine::Content(line) => Some(line.number), DisplayLine::Separator => None, } } pub fn is_match(&self) -> bool { match self { DisplayLine::Content(line) => line.name_match.is_some(), DisplayLine::Separator => false, } } } impl SyntacticView { /// Return a prepared text view with syntax coloring if possible. /// May return Ok(None) only when a pattern is given and there /// was an event before the end of filtering. pub fn new( path: &Path, pattern: InputPattern, dam: &mut Dam, con: &AppContext, no_style: bool, ) -> Result, ProgramError> { let mut sv = Self { path: path.to_path_buf(), pattern, lines: Vec::new(), scroll: 0, page_height: 0, selection_idx: None, content_lines_count: 0, total_lines_count: 0, }; if sv.read_lines(dam, con, no_style)? { sv.select_first(); Ok(Some(sv)) } else { Ok(None) } } /// Return true when there was no interruption fn read_lines( &mut self, dam: &mut Dam, con: &AppContext, no_style: bool, ) -> Result { let f = File::open(&self.path)?; { // if we detect the file isn't mappable, we'll // let the ZeroLenFilePreview try to read it let mmap = unsafe { Mmap::map(&f) }; if mmap.is_err() { return Err(ProgramError::UnmappableFile); } } let md = f.metadata()?; if md.len() == 0 { return Err(ProgramError::ZeroLenFile); } let with_style = !no_style && md.len() < MAX_SIZE_FOR_STYLING; let mut reader = BufReader::new(f); let mut content_lines = Vec::new(); let mut line = String::new(); self.total_lines_count = 0; let mut offset = 0; let mut number = 0; static SYNTAXER: Lazy = Lazy::new(Syntaxer::default); let mut highlighter = if with_style { SYNTAXER.highlighter_for(&self.path, con) } else { None }; let pattern = &self.pattern.pattern; while reader.read_line(&mut line)? > 0 { number += 1; self.total_lines_count += 1; let start = offset; offset += line.len(); for c in line.chars() { if !is_char_printable(c) { debug!("unprintable char: {:?}", c); return Err(ProgramError::UnprintableFile); } } // We don't remove '\n' or '\r' at this point because some syntax sets // need them for correct detection of comments. See #477 // Those chars are removed on printing let name_match = pattern.search_string(&line); let regions = if let Some(highlighter) = highlighter.as_mut() { highlighter .highlight_line(&line, &SYNTAXER.syntax_set) .map_err(|e| ProgramError::SyntectCrashed { details: e.to_string() })? .iter() .map(Region::from_syntect) .collect() } else { Vec::new() }; content_lines.push(Line { regions, start, len: line.len(), name_match, number, }); line.clear(); if dam.has_event() { info!("event interrupted preview filtering"); return Ok(false); } } let mut must_add_separators = false; if !pattern.is_empty() { let lines_before = con.lines_before_match_in_preview; let lines_after = con.lines_after_match_in_preview; if lines_before + lines_after > 0 { let mut kept = vec![false; content_lines.len()]; for (i, line) in content_lines.iter().enumerate() { if line.name_match.is_some() { for j in i.saturating_sub(lines_before)..(i + lines_after + 1).min(content_lines.len()) { kept[j] = true; } } } for i in 1..kept.len() - 1 { if !kept[i] && kept[i - 1] && kept[i + 1] { kept[i] = true; } } content_lines.retain(|line| kept[line.number - 1]); must_add_separators = true; } else { content_lines.retain(|line| line.name_match.is_some()); } } self.lines.clear(); self.content_lines_count = content_lines.len(); for line in content_lines { if must_add_separators { if let Some(last_number) = self.lines.last().and_then(|l| l.line_number()) { if line.number > last_number + 1 { self.lines.push(DisplayLine::Separator); } } } self.lines.push(DisplayLine::Content(line)); } Ok(true) } /// Give the count of lines which can be seen when scrolling, /// total count including filtered ones pub fn line_counts(&self) -> (usize, usize) { (self.lines.len(), self.total_lines_count) } fn ensure_selection_is_visible(&mut self) { if self.page_height >= self.lines.len() { self.scroll = 0; } else if let Some(idx) = self.selection_idx { let padding = self.padding(); if idx < self.scroll + padding || idx + padding > self.scroll + self.page_height { if idx <= padding { self.scroll = 0; } else if idx + padding > self.lines.len() { self.scroll = self.lines.len() - self.page_height; } else if idx < self.scroll + self.page_height / 2 { self.scroll = idx - padding; } else { self.scroll = idx + padding - self.page_height; } } } } fn padding(&self) -> usize { (self.page_height / 4).min(4) } pub fn get_selected_line(&self) -> Option { self.selection_idx .and_then(|idx| self.lines.get(idx)) .and_then(|line| match line { DisplayLine::Content(line) => Some(line), DisplayLine::Separator => None, }) .and_then(|line| { File::open(&self.path) .and_then(|file| unsafe { Mmap::map(&file) }) .ok() .filter(|mmap| mmap.len() >= line.start + line.len) .and_then(|mmap| { String::from_utf8( (mmap[line.start..line.start + line.len]).to_vec(), ).ok() }) }) } pub fn get_selected_line_number(&self) -> Option { self.selection_idx .and_then(|idx| self.lines[idx].line_number()) } pub fn unselect(&mut self) { self.selection_idx = None; } pub fn try_select_y(&mut self, y: u16) -> bool { let idx = y as usize + self.scroll; if idx < self.lines.len() { self.selection_idx = Some(idx); true } else { false } } pub fn select_first(&mut self) { if !self.lines.is_empty() { self.selection_idx = Some(0); self.scroll = 0; } } pub fn select_last(&mut self) { self.selection_idx = Some(self.lines.len() - 1); if self.page_height < self.lines.len() { self.scroll = self.lines.len() - self.page_height; } } pub fn try_select_line_number(&mut self, number: LineNumber) -> bool { // this could obviously be optimized for (idx, line) in self.lines.iter().enumerate() { if line.line_number() == Some(number) { self.selection_idx = Some(idx); self.ensure_selection_is_visible(); return true; } } false } pub fn move_selection(&mut self, dy: i32, cycle: bool) { if let Some(idx) = self.selection_idx { self.selection_idx = Some(move_sel(idx, self.lines.len(), dy, cycle)); } else if !self.lines.is_empty() { self.selection_idx = Some(0) } self.ensure_selection_is_visible(); } pub fn previous_match(&mut self) { let s = self.selection_idx.unwrap_or(0); for d in 1..self.lines.len() { let idx = (self.lines.len() + s - d) % self.lines.len(); if self.lines[idx].is_match() { self.selection_idx = Some(idx); self.ensure_selection_is_visible(); return; } } } pub fn next_match(&mut self) { let s = self.selection_idx.unwrap_or(0); for d in 1..self.lines.len() { let idx = (s + d) % self.lines.len(); if self.lines[idx].is_match() { self.selection_idx = Some(idx); self.ensure_selection_is_visible(); return; } } } pub fn try_scroll( &mut self, cmd: ScrollCommand, ) -> bool { let old_scroll = self.scroll; self.scroll = cmd.apply(self.scroll, self.lines.len(), self.page_height); if let Some(idx) = self.selection_idx { if self.scroll == old_scroll { let old_selection = self.selection_idx; if cmd.is_up() { self.selection_idx = Some(0); } else { self.selection_idx = Some(self.lines.len() - 1); } return self.selection_idx == old_selection; } else if idx >= old_scroll && idx < old_scroll + self.page_height { if idx + self.scroll < old_scroll { self.selection_idx = Some(0); } else if idx + self.scroll - old_scroll >= self.lines.len() { self.selection_idx = Some(self.lines.len() - 1); } else { self.selection_idx = Some(idx + self.scroll - old_scroll); } } } self.scroll != old_scroll } pub fn max_line_number(&self) -> Option { for line in self.lines.iter().rev() { if let Some(n) = line.line_number() { return Some(n); } } None } pub fn get_content_line(&self, idx: usize) -> Option<&Line> { self.lines.get(idx).and_then(|line| match line { DisplayLine::Content(line) => Some(line), DisplayLine::Separator => None, }) } pub fn display( &mut self, w: &mut W, _screen: Screen, panel_skin: &PanelSkin, area: &Area, con: &AppContext, ) -> Result<(), ProgramError> { if area.height as usize != self.page_height { self.page_height = area.height as usize; self.ensure_selection_is_visible(); } let max_number_len = self.max_line_number() .unwrap_or(0) .to_string() .len(); let show_line_number = area.width > 55 || ( self.pattern.is_some() && area.width > 8 ); let line_count = area.height as usize; let styles = &panel_skin.styles; let normal_fg = styles.preview.get_fg() .or_else(|| styles.default.get_fg()) .unwrap_or(Color::AnsiValue(252)); let normal_bg = styles.preview.get_bg() .or_else(|| styles.default.get_bg()) .unwrap_or(Color::AnsiValue(238)); let selection_bg = styles.selected_line.get_bg() .unwrap_or(Color::AnsiValue(240)); let match_bg = styles.preview_match.get_bg().unwrap_or(Color::AnsiValue(28)); let code_width = area.width as usize - 1; // 1 char left for scrollbar let scrollbar = area.scrollbar(self.scroll, self.lines.len()); let scrollbar_fg = styles.scrollbar_thumb.get_fg() .or_else(|| styles.preview.get_fg()) .unwrap_or(Color::White); for y in 0..line_count { w.queue(cursor::MoveTo(area.left, y as u16 + area.top))?; let mut cw = CropWriter::new(w, code_width); let line_idx = self.scroll + y; let selected = self.selection_idx == Some(line_idx); let bg = if selected { selection_bg } else { normal_bg }; let mut op_mmap: Option = None; match self.lines.get(line_idx) { Some(DisplayLine::Separator) => { cw.w.queue(SetBackgroundColor(bg))?; cw.queue_unstyled_str(" ")?; cw.fill(&styles.preview_separator, &SEPARATOR_FILLING)?; } Some(DisplayLine::Content(line)) => { let mut regions = &line.regions; let regions_ur; if regions.is_empty() && line.len > 0 { if op_mmap.is_none() { let file = File::open(&self.path)?; let mmap = unsafe { Mmap::map(&file)? }; op_mmap = Some(mmap); } if op_mmap.as_ref().unwrap().len() < line.start + line.len { warn!("file truncated since parsing"); } else { // an UTF8 error can only happen if file modified during display let string = String::from_utf8( // we copy the memmap slice, as it's not immutable (op_mmap.unwrap()[line.start..line.start + line.len]).to_vec(), ) .unwrap_or_else(|_| "Bad UTF8".to_string()); regions_ur = vec![Region { fg: normal_fg, string, }]; regions = ®ions_ur; } } cw.w.queue(SetBackgroundColor(bg))?; if show_line_number { cw.queue_g_string( &styles.preview_line_number, format!(" {:w$} ", line.number, w = max_number_len), )?; } else { cw.queue_unstyled_str(" ")?; } cw.w.queue(SetBackgroundColor(bg))?; if con.show_selection_mark { cw.queue_unstyled_char(if selected { '▶' } else { ' ' })?; } if let Some(nm) = &line.name_match { let mut dec = 0; let pos = &nm.pos; let mut pos_idx: usize = 0; for content in regions { let s = content.string.trim_end_matches(is_char_end_of_line); cw.w.queue(SetForegroundColor(content.fg))?; if pos_idx < pos.len() { for (cand_idx, cand_char) in s.chars().enumerate() { if pos_idx < pos.len() && pos[pos_idx] == cand_idx + dec { cw.w.queue(SetBackgroundColor(match_bg))?; cw.queue_unstyled_char(cand_char)?; cw.w.queue(SetBackgroundColor(bg))?; pos_idx += 1; } else { cw.queue_unstyled_char(cand_char)?; } } dec += s.chars().count(); } else { cw.queue_unstyled_str(s)?; } } } else { for content in regions { cw.w.queue(SetForegroundColor(content.fg))?; cw.queue_unstyled_str(content.string.trim_end_matches(is_char_end_of_line))?; } } } None => {} } cw.fill( if selected { &styles.selected_line } else { &styles.preview }, &SPACE_FILLING, )?; w.queue(SetBackgroundColor(bg))?; if is_thumb(y + area.top as usize, scrollbar) { w.queue(SetForegroundColor(scrollbar_fg))?; w.queue(Print('▐'))?; } else { w.queue(Print(' '))?; } } Ok(()) } pub fn display_info( &mut self, w: &mut W, _screen: Screen, panel_skin: &PanelSkin, area: &Area, ) -> Result<(), ProgramError> { let width = area.width as usize; let mut s = if self.pattern.is_some() { format!("{}/{}", self.content_lines_count, self.total_lines_count) } else { format!("{}", self.total_lines_count) }; if s.len() > width { return Ok(()); } if s.len() + "lines: ".len() < width { s = format!("lines: {s}"); } w.queue(cursor::MoveTo( area.left + area.width - s.len() as u16, area.top, ))?; panel_skin.styles.default.queue(w, s)?; Ok(()) } } fn is_thumb(y: usize, scrollbar: Option<(u16, u16)>) -> bool { scrollbar.map_or(false, |(sctop, scbottom)| { let y = y as u16; sctop <= y && y <= scbottom }) } /// Tell whether the character is normal enough to be displayed by the /// syntactic view (if not we'll use a hex view) fn is_char_printable(c: char) -> bool { // the tab is printable because it's replaced by spaces c == '\t' || c == '\n' || c == '\r' || !c.is_control() } fn is_char_end_of_line(c: char) -> bool { c == '\n' || c == '\r' } broot-1.46.3/src/syntactic/syntax_theme.rs000064400000000000000000000046571046102023000167260ustar 00000000000000//! The supported syntax themes coming from syntect. //! //! This enumeration may change but right now the values are the ones from //! https://docs.rs/syntect/latest/syntect/highlighting/struct.ThemeSet.html use { crate::{ errors::ConfError, }, serde::{Deserialize, Deserializer}, std::str::FromStr, }; macro_rules! Themes { ( $($enum_name:ident: $syntect_name: literal,)* ) => { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum SyntaxTheme { $($enum_name,)* } impl FromStr for SyntaxTheme { type Err = ConfError; fn from_str(s: &str) -> Result { use crate::syntactic::SyntaxTheme::*; let s = s.to_lowercase(); $( if s == stringify!($enum_name).to_lowercase() { return Ok($enum_name); } if s == $syntect_name.to_lowercase() { return Ok($enum_name); } )* Err(ConfError::InvalidSyntaxTheme { name: s.to_string() }) } } impl SyntaxTheme { pub fn name(self) -> &'static str { use crate::syntactic::SyntaxTheme::*; match self { $($enum_name => stringify!($enum_name),)* } } pub fn syntect_name(self) -> &'static str { use crate::syntactic::SyntaxTheme::*; match self { $($enum_name => $syntect_name,)* } } } impl Default for SyntaxTheme { fn default() -> Self { Self::MochaDark } } pub static SYNTAX_THEMES: &[SyntaxTheme] = &[ $(crate::syntactic::SyntaxTheme::$enum_name,)* ]; } } Themes! { GitHub: "InspiredGitHub", SolarizedDark: "Solarized (dark)", SolarizedLight: "Solarized (light)", EightiesDark: "base16-eighties.dark", MochaDark: "base16-mocha.dark", OceanDark: "base16-ocean.dark", OceanLight: "base16-ocean.light", } impl<'de> Deserialize<'de> for SyntaxTheme { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de> { let s = String::deserialize(deserializer)?; FromStr::from_str(&s).map_err(serde::de::Error::custom) } } broot-1.46.3/src/syntactic/syntaxer.rs000064400000000000000000000032671046102023000160670ustar 00000000000000use { crate::{ app::AppContext, }, once_cell::sync::Lazy, std::path::Path, syntect::{ easy::{ HighlightLines, HighlightOptions, }, parsing::SyntaxSet, highlighting::{Theme, ThemeSet}, }, }; static SYNTAXES: &[u8] = include_bytes!("../../resources/syntect/syntaxes.bin"); pub static SYNTAXER: Lazy = Lazy::new(Syntaxer::default); /// wrap heavy to initialize syntect things pub struct Syntaxer { pub syntax_set: SyntaxSet, pub theme_set: ThemeSet, } impl Default for Syntaxer { fn default() -> Self { Self { syntax_set: time!( Debug, syntect::dumps::from_uncompressed_data(SYNTAXES).unwrap() ), theme_set: ThemeSet::load_defaults(), } } } impl Syntaxer { pub fn available_themes( &self ) -> std::collections::btree_map::Keys { self.theme_set.themes.keys() } pub fn highlighter_for( &self, path: &Path, con: &AppContext, ) -> Option> { path.extension() .and_then(|e| e.to_str()) .and_then(|ext| self.syntax_set.find_syntax_by_extension(ext)) .map(|syntax| { let theme = con.syntax_theme.unwrap_or_default(); let theme = self.theme_set.themes.get(theme.syntect_name()) .unwrap_or_else(|| self.theme_set.themes.iter().next().unwrap().1); let options = HighlightOptions { ignore_errors: true, }; HighlightLines::new(syntax, theme, options) }) } } broot-1.46.3/src/task_sync.rs000064400000000000000000000135701046102023000142050ustar 00000000000000use { std::thread, termimad::{ crossbeam::channel::{self, bounded, select, Receiver}, TimedEvent, }, }; pub enum Either { First(A), Second(B), } #[derive(Debug, Clone)] pub enum ComputationResult { NotComputed, // not computed but will probably be Done(V), None, // nothing to compute, cancelled, failed, etc. } impl ComputationResult { pub fn is_done(&self) -> bool { matches!(&self, Self::Done(_)) } pub fn is_not_computed(&self) -> bool { matches!(&self, Self::NotComputed) } pub fn is_some(&self) -> bool { !matches!(&self, Self::None) } pub fn is_none(&self) -> bool { matches!(&self, Self::None) } } /// The dam controls the flow of events. /// A dam is used in broot to manage long computations and, /// when the user presses a key, either tell the computation /// to stop (the computation function checking `has_event`) /// or drop the computation. pub struct Dam { receiver: Receiver, in_dam: Option, } impl Dam { pub fn from(receiver: Receiver) -> Self { Self { receiver, in_dam: None, } } pub fn unlimited() -> Self { Self::from(channel::never()) } /// provide an observer which can be used for periodic /// check a task can be used. /// The observer can safely be moved to another thread /// but Be careful not to use it /// after the event listener started again. In any case /// using try_compute should be preferred for immediate /// return to the ui thread. pub fn observer(&self) -> DamObserver { DamObserver::from(self) } /// launch the computation on a new thread and return /// when it finishes or when a new event appears on /// the channel. /// Note that the task itself isn't interrupted so that /// this should not be used when many tasks are expected /// to be launched (or it would result in many working /// threads uselessly working in the background) : use /// dam.has_event from inside the task whenever possible. pub fn try_compute ComputationResult>( &mut self, f: F, ) -> ComputationResult { let (comp_sender, comp_receiver) = bounded(1); thread::spawn(move || { let comp_res = time!("comp in dam", f()); if comp_sender.send(comp_res).is_err() { debug!("no channel at end of computation"); } }); self.select(comp_receiver) } pub fn select( &mut self, comp_receiver: Receiver>, ) -> ComputationResult { if self.in_dam.is_some() { // should probably not happen debug!("There's already an event in dam"); ComputationResult::None } else { select! { recv(self.receiver) -> event => { // interruption debug!("dam interrupts computation"); self.in_dam = event.ok(); ComputationResult::None } recv(comp_receiver) -> comp_res => { // computation finished comp_res.unwrap_or(ComputationResult::None) } } } } /// non blocking pub fn has_event(&self) -> bool { !self.receiver.is_empty() } /// drop all events, returns the count of removed events pub fn clear(&mut self) -> usize { let mut n = 0; while self.has_event() { n += 1; self.next_event(); } n } /// block until next event (including the one which /// may have been pushed back into the dam). /// no event means the source is dead (i.e. we /// must quit broot) /// There's no event kept in dam after this call. pub fn next_event(&mut self) -> Option { if self.in_dam.is_some() { self.in_dam.take() } else { match self.receiver.recv() { Ok(event) => Some(event), Err(_) => { debug!("dead dam"); // should be logged once None } } } } // or maybe return either Option or Option ? pub fn next(&mut self, other: &Receiver) -> Either, Option> { if self.in_dam.is_some() { Either::First(self.in_dam.take()) } else { select! { recv(self.receiver) -> event => Either::First(match event { Ok(event) => Some(event), Err(_) => { debug!("dead dam"); // should be logged once None } }), recv(other) -> o => Either::Second(match o { Ok(o) => Some(o), Err(_) => { debug!("dead other"); None } }), } } } } pub struct DamObserver { receiver: Receiver, } impl DamObserver { pub fn from(dam: &Dam) -> Self { Self { receiver: dam.receiver.clone(), } } /// be careful that this can be used as a thread /// stop condition only before the event receiver /// start being active to avoid a race condition. pub fn has_event(&self) -> bool { !self.receiver.is_empty() } } /// wraps either a computation in progress, or a finished /// one (even a failed or useless one). /// This can be stored in a map to avoid starting computations /// more than once. #[derive(Debug, Clone)] pub enum Computation { InProgress(Receiver>), Finished(ComputationResult), } broot-1.46.3/src/terminal.rs000064400000000000000000000023231046102023000140140ustar 00000000000000use { crate::{ app::*, display::W, verb::*, }, std::io::Write, }; /// Change the terminal's title if broot was configured with /// a `terminal_title` entry #[inline] pub fn update_title( w: &mut W, app_state: &AppState, con: &AppContext, ) { if let Some(pattern) = &con.terminal_title_pattern { set_title(w, pattern, app_state, con); } } /// Reset the terminal's title to its default value (which may be the one /// just before broot was launched, but may also be different) pub fn reset_title( w: &mut W, con: &AppContext, ) { if con.terminal_title_pattern.is_some() && con.reset_terminal_title_on_exit { let _ = write!(w, "\u{1b}]2;\u{07}"); let _ = w.flush(); } } fn set_title( w: &mut W, pattern: &ExecPattern, app_state: &AppState, con: &AppContext, ) { let builder = ExecutionStringBuilder::without_invocation( SelInfo::from_path(&app_state.root), app_state, ); let title = builder.shell_exec_string(pattern, con); set_title_str(w, &title) } #[inline] fn set_title_str( w: &mut W, title: &str, ) { let _ = write!(w, "\u{1b}]0;{title}\u{07}"); let _ = w.flush(); } broot-1.46.3/src/trash/mod.rs000064400000000000000000000017441046102023000141070ustar 00000000000000 mod trash_sort; mod trash_state; mod trash_state_cols; pub use trash_state::*; use { trash::{ self as trash_crate, TrashItem, TrashItemSize, }, }; /// Determine whether an item in the trash is a directory. /// /// There's probably a simpler solution in the trash crate, but I didn't found it. fn item_is_dir(item: &TrashItem) -> bool { match trash_crate::os_limited::metadata(item) { Ok(metadata) => { match metadata.size { TrashItemSize::Bytes(_) => false, TrashItemSize::Entries(_) => true, } } Err(_) => false, } } /// Return either the byte size or the number of entries of a trash item. /// /// Return None when it couldn't be determined. fn item_unified_size(item: &TrashItem) -> Option { match trash_crate::os_limited::metadata(item).ok()?.size { TrashItemSize::Bytes(v) => Some(v), TrashItemSize::Entries(v) => v.try_into().ok(), } } broot-1.46.3/src/trash/trash_sort.rs000064400000000000000000000011741046102023000155150ustar 00000000000000use { super::*, crate::{ tree::*, }, trash::{ TrashItem, }, }; /// Sort trash items according to the current tree options. pub fn sort( items: &mut [TrashItem], tree_options: &TreeOptions, ) { info!("sorting itemsi by {:?}", tree_options.sort); match tree_options.sort { Sort::Date => items.sort_by_key(|item| std::cmp::Reverse(item.time_deleted)), Sort::Size => items.sort_by_key(|item| std::cmp::Reverse( item_unified_size(item).unwrap_or(0) )), _ => items.sort_by_key(|item| (item.name.clone(), item.original_parent.clone())), } } broot-1.46.3/src/trash/trash_state.rs000064400000000000000000000523211046102023000156460ustar 00000000000000use { super::{ item_is_dir, trash_sort::*, trash_state_cols::*, }, crate::{ app::*, command::*, display::*, errors::ProgramError, pattern::*, tree::TreeOptions, verb::*, }, crokey::crossterm::{ cursor, style::Color, QueueableCommand, }, std::{ ffi::OsString, path::Path, }, termimad::{ minimad::Alignment, *, }, trash::{ self as trash_crate, TrashItem, }, unicode_width::UnicodeWidthStr, }; struct FilteredContent { pattern: Pattern, items: Vec, selection_idx: Option, } /// an application state showing the content of the trash pub struct TrashState { items: Vec, selection_idx: Option, scroll: usize, page_height: usize, tree_options: TreeOptions, filtered: Option, mode: Mode, } impl TrashState { /// create a state listing the content of the system's trash pub fn new( tree_options: TreeOptions, con: &AppContext, ) -> Result { let mut items = trash::os_limited::list() .map_err(|e| ProgramError::Trash { message: e.to_string() })?; sort(&mut items, &tree_options); let selection_idx = None; Ok(TrashState { items, selection_idx, scroll: 0, page_height: 0, tree_options, filtered: None, mode: con.initial_mode(), }) } fn modified( &self, options: TreeOptions, message: Option<&'static str>, in_new_panel: bool, con: &AppContext, ) -> CmdResult { match Self::new(options, con) { Ok(mut ts) => { let old_selection = self.selected_item_id(); ts.select_item_by_id(old_selection.as_ref()); if in_new_panel { CmdResult::NewPanel { state: Box::new(ts), purpose: PanelPurpose::None, direction: HDir::Right, } } else { CmdResult::NewState { state: Box::new(ts), message, } } } Err(e) => CmdResult::error(e.to_string()), } } pub fn count(&self) -> usize { self.filtered .as_ref() .map(|f| f.items.len()) .unwrap_or_else(|| self.items.len().into()) } pub fn try_scroll( &mut self, cmd: ScrollCommand, ) -> bool { let old_scroll = self.scroll; self.scroll = cmd.apply(self.scroll, self.count(), self.page_height); // move selection to an item in view if let Some(f) = self.filtered.as_mut() { if let Some(idx) = f.selection_idx { if idx < self.scroll { f.selection_idx = Some(self.scroll); } else if idx >= self.scroll + self.page_height { f.selection_idx = Some(self.scroll + self.page_height - 1); } } } else { if let Some(idx) = self.selection_idx { if idx < self.scroll { self.selection_idx = Some(self.scroll); } else if idx >= self.scroll + self.page_height { self.selection_idx = Some(self.scroll + self.page_height - 1); } } } self.scroll != old_scroll } /// If there's a selection, adjust the scroll to make it visible pub fn show_selection(&mut self) { let selection_idx = if let Some(f) = self.filtered.as_ref() { f.selection_idx } else { self.selection_idx }; if let Some(idx) = selection_idx { if idx < self.scroll { self.scroll = idx; } else if idx >= self.scroll + self.page_height { self.scroll = idx - self.page_height + 1; } } } /// change the selection fn move_line( &mut self, internal_exec: &InternalExecution, input_invocation: Option<&VerbInvocation>, dir: i32, // -1 for up, 1 for down cycle: bool, ) -> CmdResult { let count = get_arg(input_invocation, internal_exec, 1); let dec = dir * count; let selection_idx; if let Some(f) = self.filtered.as_mut() { selection_idx = if let Some(idx) = f.selection_idx { Some(move_sel(idx, f.items.len(), dec, cycle)) } else if !f.items.is_empty() { Some(if dec > 0 { 0 } else { f.items.len() - 1 }) } else { None }; f.selection_idx = selection_idx; } else { selection_idx = if let Some(idx) = self.selection_idx { Some(move_sel(idx, self.items.len(), dec, cycle)) } else if !self.items.is_empty() { Some(if dec > 0 { 0 } else { self.items.len() - 1 }) } else { None }; self.selection_idx = selection_idx; } if let Some(selection_idx) = selection_idx { if selection_idx < self.scroll { self.scroll = selection_idx; } else if selection_idx >= self.scroll + self.page_height { self.scroll = selection_idx + 1 - self.page_height; } } CmdResult::Keep } fn selected_item(&self) -> Option<&TrashItem> { if let Some(f) = self.filtered.as_ref() { f.selection_idx.map(|idx| &f.items[idx]) } else { self.selection_idx.map(|idx| &self.items[idx]) } } fn selected_item_id(&self) -> Option { self.selected_item().map(|i| i.id.clone()) } fn select_item_by_id(&mut self, id: Option<&OsString>) { self.selection_idx = id.and_then(|id| self.items.iter().position(|i| &i.id == id)); } fn take_selected_item(&mut self) -> Option { if let Some(f) = self.filtered.as_mut() { if let Some(idx) = f.selection_idx { let item = f.items.remove(idx); if f.items.is_empty() { f.selection_idx = None; } else if idx == f.items.len() { f.selection_idx = Some(idx - 1); } Some(item) } else { None } } else { if let Some(idx) = self.selection_idx { let item = self.items.remove(idx); if self.items.is_empty() { self.selection_idx = None; } else if idx == self.items.len() { self.selection_idx = Some(idx - 1); } Some(item) } else { None } } } } impl PanelState for TrashState { fn get_type(&self) -> PanelStateType { PanelStateType::Trash } fn set_mode( &mut self, mode: Mode, ) { self.mode = mode; } fn get_mode(&self) -> Mode { self.mode } /// We don't want to expose path to verbs because you can't /// normally access files in the trash fn selected_path(&self) -> Option<&Path> { None } fn tree_options(&self) -> TreeOptions { self.tree_options.clone() } fn with_new_options( &mut self, _screen: Screen, change_options: &dyn Fn(&mut TreeOptions) -> &'static str, in_new_panel: bool, con: &AppContext, ) -> CmdResult { let mut options = self.tree_options.clone(); let message = change_options(&mut options); let message = Some(message); self.modified( options, message, in_new_panel, con, ) } /// We don't want to expose path to verbs because you can't /// normally access files in the trash fn selection(&self) -> Option> { None } fn refresh( &mut self, _screen: Screen, _con: &AppContext, ) -> Command { let old_selection = self.selected_item_id(); if let Ok(mut items) = trash::os_limited::list() { sort(&mut items, &self.tree_options); self.items = items; self.scroll = 0; self.select_item_by_id(old_selection.as_ref()); } Command::empty() } fn on_pattern( &mut self, pattern: InputPattern, _app_state: &AppState, _con: &AppContext, ) -> Result { if pattern.is_none() { if let Some(f) = self.filtered.take() { if let Some(idx) = f.selection_idx { self.selection_idx = self.items .iter() .position(|m| m.id == f.items[idx].id); } } } else { let pattern = pattern.pattern; let mut best_score = 0; let mut selection_idx = None; let mut items = Vec::new(); for item in &self.items { let score = pattern.score_of_string(&item.name).unwrap_or(0) + pattern .score_of_string(&item.original_parent.to_string_lossy()) .unwrap_or(0); if score > 0 { items.push(item.clone()); if score > best_score { best_score = score; selection_idx = Some(items.len() - 1); } } } self.filtered = Some(FilteredContent { pattern, items, selection_idx, }); } self.show_selection(); Ok(CmdResult::Keep) } fn display( &mut self, w: &mut W, disc: &DisplayContext, ) -> Result<(), ProgramError> { let area = &disc.state_area; let con = &disc.con; self.page_height = area.height as usize - 2; let (items, selection_idx) = if let Some(filtered) = &self.filtered { (filtered.items.as_slice(), filtered.selection_idx) } else { (self.items.as_slice(), self.selection_idx) }; let scrollbar = area.scrollbar(self.scroll, items.len()); //- style preparation let styles = &disc.panel_skin.styles; let selection_bg = styles .selected_line .get_bg() .unwrap_or(Color::AnsiValue(240)); let match_style = &styles.char_match; let mut selected_match_style = styles.char_match.clone(); selected_match_style.set_bg(selection_bg); let border_style = &styles.help_table_border; let mut selected_border_style = styles.help_table_border.clone(); selected_border_style.set_bg(selection_bg); //- width computations let width = area.width as usize; let available_width = if con.show_selection_mark { width - 1 } else { width }; let cols = get_cols(items, available_width, &self.tree_options); let first_col_width = cols.iter().filter_map(|c| c.size()).next().unwrap_or(0); //- titles w.queue(cursor::MoveTo(area.left, area.top))?; let mut cw = CropWriter::new(w, width); if con.show_selection_mark { cw.queue_char(&styles.default, ' ')?; } let mut added = false; for col in &cols { let Some(size) = col.size() else { continue; }; if added { cw.queue_char(border_style, '│')?; } else { added = true; } let title = col.content().title(); let title = if title.len() > size { &title[..size] } else { title }; cw.queue_g_string(&styles.default, format!("{:^size$}", title))?; } cw.fill(border_style, &SPACE_FILLING)?; //- horizontal line w.queue(cursor::MoveTo(area.left, 1 + area.top))?; let mut cw = CropWriter::new(w, width); if con.show_selection_mark { cw.queue_char(&styles.default, ' ')?; } let mut added = false; for col in &cols { let Some(size) = col.size() else { continue; }; if added { cw.queue_char(border_style, '┼')?; } else { added = true; } cw.queue_g_string(border_style, format!("{:─>width$}", "", width = size))?; } cw.fill(border_style, &BRANCH_FILLING)?; //- content let mut idx = self.scroll; for y in 2..area.height { w.queue(cursor::MoveTo(area.left, y + area.top))?; let selected = selection_idx == Some(idx); let mut cw = CropWriter::new(w, width - 1); // -1 for scrollbar let txt_style = if selected { &styles.selected_line } else { &styles.default }; if let Some(item) = items.get(idx) { let is_dir = item_is_dir(item); let match_style = if selected { &selected_match_style } else { match_style }; let border_style = if selected { &selected_border_style } else { border_style }; if con.show_selection_mark { cw.queue_char(txt_style, if selected { '▶' } else { ' ' })?; } let mut added = false; for col in &cols { let Some(size) = col.size() else { continue; }; if added { cw.queue_char(border_style, '│')?; } else { added = true; } let value = col.content().value_of(item, &self.tree_options); let style = col.content().style(is_dir, styles); let mut cloned_style; let style = if selected { cloned_style = style.clone(); if let Some(c) = styles.selected_line.get_bg() { cloned_style.set_bg(c); } &cloned_style } else { &style }; let mut matched_string = MatchedString::new( self.filtered .as_ref() .and_then(|f| f.pattern.search_string(&value)), &value, style, match_style, ); if value.width() > size { cw.queue_char(txt_style, '…')?; matched_string.cut_left_to_fit(size - 1); matched_string.queue_on(&mut cw)?; } else { matched_string.fill(size, Alignment::Left); matched_string.queue_on(&mut cw)?; } } idx += 1; } else { if con.show_selection_mark { cw.queue_char(&styles.default, ' ')?; } cw.queue_g_string(border_style, format!("{: >width$}", '│', width = first_col_width + 1))?; } cw.fill(txt_style, &SPACE_FILLING)?; let scrollbar_style = if ScrollCommand::is_thumb(y, scrollbar) { &styles.scrollbar_thumb } else { &styles.scrollbar_track }; scrollbar_style.queue_str(w, "▐")?; } Ok(()) } fn on_internal( &mut self, w: &mut W, invocation_parser: Option<&InvocationParser>, internal_exec: &InternalExecution, input_invocation: Option<&VerbInvocation>, trigger_type: TriggerType, app_state: &mut AppState, cc: &CmdContext, ) -> Result { use Internal::*; Ok(match internal_exec.internal { Internal::restore_trashed_file => { if let Some(item) = self.selected_item() { match trash_crate::os_limited::restore_all([item.clone()]) { Ok(_) => { let path = item.original_path(); self.take_selected_item(); CmdResult::Message(format!( "File *{}* restored", path.to_string_lossy(), )) } Err(trash_crate::Error::RestoreCollision { path, .. }) => { CmdResult::DisplayError(format!( "collision: *{}* already exists", path.to_string_lossy(), )) } Err(e) => { CmdResult::DisplayError(format!( "restore failed: {}", e.to_string(), )) } } } else { CmdResult::DisplayError( "an item must be selected".to_string(), ) } } Internal::delete_trashed_file => { if let Some(item) = self.selected_item() { match trash_crate::os_limited::purge_all([item.clone()]) { Ok(_) => { let path = item.original_path(); self.take_selected_item(); CmdResult::Message(format!( "File *{}* restored", path.to_string_lossy(), )) } Err(e) => { CmdResult::DisplayError(format!( "deletion failed: {}", e.to_string(), )) } } } else { CmdResult::DisplayError( "an item must be selected".to_string(), ) } } Internal::back => { if let Some(f) = self.filtered.take() { if let Some(idx) = f.selection_idx { self.selection_idx = self.items .iter() .position(|m| m.id == f.items[idx].id); } self.show_selection(); CmdResult::Keep } else { CmdResult::PopState } } Internal::line_down => self.move_line(internal_exec, input_invocation, 1, true), Internal::line_up => self.move_line(internal_exec, input_invocation, -1, true), Internal::line_down_no_cycle => { self.move_line(internal_exec, input_invocation, 1, false) } Internal::line_up_no_cycle => { self.move_line(internal_exec, input_invocation, -1, false) } Internal::open_stay => { // it would probably be a good idea to bind enter to restore_trash_file ? CmdResult::DisplayError("can't open a file from the trash".to_string()) } Internal::panel_left_no_open => CmdResult::HandleInApp(Internal::panel_left_no_open), Internal::panel_right_no_open => CmdResult::HandleInApp(Internal::panel_right_no_open), Internal::page_down => { if !self.try_scroll(ScrollCommand::Pages(1)) { self.selection_idx = Some(self.count() - 1); } CmdResult::Keep } Internal::page_up => { if !self.try_scroll(ScrollCommand::Pages(-1)) { self.selection_idx = Some(0); } CmdResult::Keep } open_leave => CmdResult::PopStateAndReapply, _ => self.on_internal_generic( w, invocation_parser, internal_exec, input_invocation, trigger_type, app_state, cc, )?, }) } fn on_click( &mut self, _x: u16, y: u16, _screen: Screen, _con: &AppContext, ) -> Result { if y >= 2 { let y = y as usize - 2 + self.scroll; let len: usize = self.items.len().into(); if y < len { self.selection_idx = Some(y); } } Ok(CmdResult::Keep) } } broot-1.46.3/src/trash/trash_state_cols.rs000064400000000000000000000100321046102023000166570ustar 00000000000000use { super::*, crate::{ skin::StyleMap, tree::TreeOptions, }, chrono::{ Local, LocalResult, TimeZone, }, termimad::CompoundStyle, trash::TrashItem, unicode_width::UnicodeWidthStr, }; /// A displayable column, related to properties of the TrashItem #[derive(Debug, Clone, Copy)] pub enum TrashItemProperty { OriginalParent, Name, DeletionDate, Size, } impl TrashItemProperty { pub fn title(self) -> &'static str { // only single byte characters allowed here match self { Self::OriginalParent => "Original parent", Self::Name => "Deleted file name", Self::DeletionDate => "Deletion", Self::Size => "Size", } } pub fn style<'s>( self, is_dir: bool, styles: &'s StyleMap, ) -> &'s CompoundStyle { match self { Self::DeletionDate => &styles.dates, _ => { if is_dir { &styles.directory } else { &styles.file } } } } pub fn value_of( self, item: &TrashItem, options: &TreeOptions, ) -> String { match self { Self::OriginalParent => item.original_parent.to_string_lossy().to_string(), Self::Name => item.name.clone(), Self::DeletionDate => { let seconds = item.time_deleted; if let LocalResult::Single(date_time) = Local.timestamp_opt(seconds, 0) { date_time.format(options.date_time_format).to_string() } else { "???".to_string() } } Self::Size => match item_unified_size(item) { Some(size) => format!("{:>4}", file_size::fit_4(size)), None => "????".to_string(), }, } } pub fn const_width(self) -> bool { match self { Self::OriginalParent => false, Self::Name => false, Self::DeletionDate => true, Self::Size => true, } } pub fn optimal_width( self, items: &[TrashItem], options: &TreeOptions, ) -> usize { match self { Self::Size => 4, _ => items .iter() .map(|m| self.value_of(m, options).width()) .max() .unwrap_or(0), } } pub fn column_constraints( self, items: &[TrashItem], options: &TreeOptions, ) -> flex_grow::Child { let optimal_width = self.optimal_width(items, options); let child = flex_grow::Child::new(self); if self.const_width() { child.with_size(optimal_width) } else { child.with_max(optimal_width) } } } pub fn get_cols( items: &[TrashItem], available_width: usize, tree_options: &TreeOptions, ) -> Vec> { let mut cols_builder = flex_grow::Container::builder_in(available_width).with_margin_between(1); if tree_options.show_sizes { cols_builder.add( TrashItemProperty::Size .column_constraints(items, tree_options) .optional(), ); } if tree_options.show_dates { cols_builder.add( TrashItemProperty::DeletionDate .column_constraints(items, tree_options) .optional(), ); } cols_builder.add( TrashItemProperty::OriginalParent .column_constraints(items, tree_options) .with_min(10) .optional(), ); cols_builder.add( TrashItemProperty::Name .column_constraints(items, tree_options) .with_min(10) .with_grow(2.0), ); let Ok(cols) = cols_builder.build() else { return Vec::new(); // should not happen }; debug!("trash_state cols: {:?}", cols.sizes()); cols.to_children() } broot-1.46.3/src/tree/mod.rs000064400000000000000000000003131046102023000137140ustar 00000000000000 mod sort; mod tree; mod tree_line; mod tree_line_type; mod tree_options; pub use { sort::Sort, tree::Tree, tree_line::*, tree_line_type::TreeLineType, tree_options::TreeOptions, }; broot-1.46.3/src/tree/sort.rs000064400000000000000000000011171046102023000141270ustar 00000000000000 /// A sort key. /// A non None sort mode implies only one level of the tree /// is displayed. /// When in None mode, paths are alpha sorted #[derive(Debug, Clone, Copy, PartialEq)] pub enum Sort { None, Count, Date, Size, TypeDirsFirst, TypeDirsLast, } impl Sort { pub fn prevent_deep_display(self) -> bool { match self { Self::None => false, Self::Count => true, Self::Date => true, Self::Size => true, Self::TypeDirsFirst => false, Self::TypeDirsLast => false, } } } broot-1.46.3/src/tree/tree.rs000064400000000000000000000546251046102023000141130ustar 00000000000000use { super::*, crate::{ app::AppContext, errors::TreeBuildError, file_sum::FileSum, git::TreeGitStatus, task_sync::ComputationResult, task_sync::Dam, tree_build::{BuildReport, TreeBuilder}, }, rustc_hash::FxHashMap, std::{ cmp::Ord, mem, path::{Path, PathBuf}, }, }; /// The tree which may be displayed, with one line per visible line of the panel. /// /// In the tree structure, every "node" is just a line, there's /// no link from a child to its parent or from a parent to its children. #[derive(Debug, Clone)] pub struct Tree { pub lines: Vec, pub next_line_id: usize, pub selection: usize, // there's always a selection (starts with root, which is 0) pub options: TreeOptions, pub scroll: usize, // the number of lines at the top hidden because of scrolling pub total_search: bool, // whether the search was made on all children pub git_status: ComputationResult, pub build_report: BuildReport, } impl Tree { /// rebuild the tree with the same root, height, and options pub fn refresh( &mut self, page_height: usize, con: &AppContext, ) -> Result<(), TreeBuildError> { let builder = TreeBuilder::from( self.root().to_path_buf(), self.options.clone(), page_height, con, )?; self.total_search = false; // on refresh we always do a non total search let mut tree = builder .build_tree( self.total_search, &Dam::unlimited(), ) .unwrap(); // should not fail let selected_path = self.selected_line().path.to_path_buf(); mem::swap(&mut self.lines, &mut tree.lines); self.scroll = 0; if !self.try_select_path(&selected_path) && self.selection >= self.lines.len() { self.selection = 0; } self.make_selection_visible(page_height); Ok(()) } /// do what must be done after line additions or removals: /// - sort the lines /// - compute left branches pub fn after_lines_changed(&mut self) { // we need to order the lines to build the tree. // It's a little complicated because // - we want a case insensitive sort // - we still don't want to confuse the children of AA and Aa // - a node can come from a not parent node, when we followed a link let mut id_parents: FxHashMap = FxHashMap::default(); let mut id_lines: FxHashMap = FxHashMap::default(); for line in &self.lines[..] { if let Some(parent_id) = line.parent_id { id_parents.insert(line.id, parent_id); } id_lines.insert(line.id, line); } let mut sort_paths: FxHashMap = FxHashMap::default(); for line in &self.lines[1..] { let mut sort_path = String::new(); let mut id = line.id; while let Some(l) = id_lines.get(&id) { let lower_name = l.path.file_name().map_or( "".to_string(), |name| name.to_string_lossy().to_lowercase(), ); let sort_prefix = match self.options.sort { Sort::TypeDirsFirst => { if l.is_dir() { " " } else { l.path.extension().and_then(|s| s.to_str()).unwrap_or("") } } Sort::TypeDirsLast => { if l.is_dir() { "~~~~~~~~~~~~~~" } else { l.path.extension().and_then(|s| s.to_str()).unwrap_or("") } } _ => { "" } }; sort_path = format!( "{}{}-{}/{}", sort_prefix, lower_name, id, // to be sure to separate paths having the same lowercase sort_path, ); if let Some(&parent_id) = id_parents.get(&id) { id = parent_id; } else { break; } } sort_paths.insert(line.id, sort_path); } self.lines[1..].sort_by_key(|line| sort_paths.get(&line.id).unwrap()); let mut best_index = 0; // index of the line with the best score for i in 1..self.lines.len() { if self.lines[i].score > self.lines[best_index].score { best_index = i; } for d in 0..self.lines[i].left_branches.len() { self.lines[i].left_branches[d] = false; } } // then we discover the branches (for the drawing) // and we mark the last children as pruning, if they have unlisted brothers let mut last_parent_index: usize = self.lines.len() + 1; for end_index in (1..self.lines.len()).rev() { let depth = (self.lines[end_index].depth - 1) as usize; let start_index = { let parent_index = match self.lines[end_index].parent_id { Some(parent_id) => { let mut index = end_index; loop { index -= 1; if self.lines[index].id == parent_id { break; } if index == 0 { break; } } index } None => end_index, // Should not happen }; if parent_index != last_parent_index { // the line at end_index is the last listed child of the line at parent_index let unlisted = self.lines[parent_index].unlisted; if unlisted > 0 && self.lines[end_index].nb_kept_children == 0 { if best_index == end_index { //debug!("Avoiding to prune the line with best score"); } else { //debug!("turning {:?} into Pruning", self.lines[end_index].path); self.lines[end_index].line_type = TreeLineType::Pruning; self.lines[end_index].unlisted = unlisted + 1; self.lines[end_index].name = format!("{} unlisted", unlisted + 1); self.lines[parent_index].unlisted = 0; } } last_parent_index = parent_index; } parent_index + 1 }; for i in start_index..=end_index { self.lines[i].left_branches[depth] = true; } } if self.options.needs_sum() { time!("fetch_file_sum", self.fetch_regular_file_sums()); // not the dirs, only simple files self.sort_siblings(); // does nothing when sort mode is None } } pub fn is_empty(&self) -> bool { self.lines.len() == 1 } pub fn has_branch(&self, line_index: usize, depth: usize) -> bool { if line_index >= self.lines.len() { return false; } let line = &self.lines[line_index]; depth < usize::from(line.depth) && line.left_branches[depth] } /// select another line /// /// For example the following one if dy is 1. pub fn move_selection(&mut self, dy: i32, page_height: usize, cycle: bool) { let l = self.lines.len(); // we find the new line to select loop { if dy < 0 { let ady = (-dy) as usize; if !cycle && self.selection < ady { break; } self.selection = (self.selection + l - ady) % l; } else { let dy = dy as usize; if !cycle && self.selection + dy >= l { break; } self.selection = (self.selection + dy) % l; } if self.lines[self.selection].is_selectable() { break; } } // we adjust the scroll if l > page_height { if self.selection < 3 { self.scroll = 0; } else if self.selection < self.scroll + 3 { self.scroll = self.selection - 3; } else if self.selection + 3 > l { self.scroll = l - page_height; } else if self.selection + 3 > self.scroll + page_height { self.scroll = self.selection + 3 - page_height; } } } /// Scroll the desired amount and return true, or return false if it's /// already at end or the tree fits the page pub fn try_scroll(&mut self, dy: i32, page_height: usize) -> bool { if self.lines.len() <= page_height { return false; } if dy < 0 { // scroll up if self.scroll == 0 { return false; } let ady = -dy as usize; if ady < self.scroll { self.scroll -= ady; } else { self.scroll = 0; } } else { // scroll down let max = self.lines.len() - page_height; if self.scroll >= max { return false; } self.scroll = (self.scroll + dy as usize).min(max); } self.select_visible_line(page_height); true } /// try to select a line by index of visible line /// (works if y+scroll falls on a selectable line) pub fn try_select_y(&mut self, y: usize) -> bool { let y = y + self.scroll; if y < self.lines.len() && self.lines[y].is_selectable() { self.selection = y; return true; } false } /// fix the selection so that it's a selectable visible line fn select_visible_line(&mut self, page_height: usize) { if self.selection < self.scroll || self.selection >= self.scroll + page_height { self.selection = self.scroll; let l = self.lines.len(); loop { self.selection = (self.selection + l + 1) % l; if self.lines[self.selection].is_selectable() { break; } } } } pub fn make_selection_visible(&mut self, page_height: usize) { if page_height >= self.lines.len() || self.selection < 3 { self.scroll = 0; } else if self.selection <= self.scroll { self.scroll = self.selection - 2; } else if self.selection > self.lines.len() - 2 { self.scroll = self.lines.len() - page_height; } else if self.selection >= self.scroll + page_height { self.scroll = self.selection + 2 - page_height; } } pub fn selected_line(&self) -> &TreeLine { &self.lines[self.selection] } pub fn root(&self) -> &PathBuf { &self.lines[0].path } pub fn is_root_selected(&self) -> bool { self.selection == 0 } /// select the line with the best matching score pub fn try_select_best_match(&mut self) { let mut best_score = 0; for (idx, line) in self.lines.iter().enumerate() { if !line.is_selectable() { continue; } if best_score > line.score { continue; } if line.score == best_score { // in case of equal scores, we prefer the shortest path if self.lines[idx].depth >= self.lines[self.selection].depth { continue; } } best_score = line.score; self.selection = idx; } } /// return true when we could select the given path pub fn try_select_path(&mut self, path: &Path) -> bool { for (idx, line) in self.lines.iter().enumerate() { if !line.is_selectable() { continue; } if path == line.path { self.selection = idx; return true; } } false } pub fn try_select_first(&mut self) -> bool { for idx in 0..self.lines.len() { let line = &self.lines[idx]; if line.is_selectable() { self.selection = idx; self.scroll = 0; return true; } } false } pub fn try_select_last(&mut self, page_height: usize) -> bool { for idx in (0..self.lines.len()).rev() { let line = &self.lines[idx]; if line.is_selectable() { self.selection = idx; self.make_selection_visible(page_height); return true; } } false } pub fn try_select_previous_same_depth(&mut self, page_height: usize) -> bool { let depth = self.lines[self.selection].depth; for di in (0..self.lines.len()).rev() { let idx = (self.selection + di) % self.lines.len(); let line = &self.lines[idx]; if !line.is_selectable() || line.depth != depth { continue; } self.selection = idx; self.make_selection_visible(page_height); return true; } false } pub fn try_select_next_same_depth(&mut self, page_height: usize) -> bool { let depth = self.lines[self.selection].depth; for di in 0..self.lines.len() { let idx = (self.selection + di + 1) % self.lines.len(); let line = &self.lines[idx]; if !line.is_selectable() || line.depth != depth { continue; } self.selection = idx; self.make_selection_visible(page_height); return true; } false } pub fn try_select_previous_filtered( &mut self, filter: F, page_height: usize, ) -> bool where F: Fn(&TreeLine) -> bool, { for di in (0..self.lines.len()).rev() { let idx = (self.selection + di) % self.lines.len(); let line = &self.lines[idx]; if !line.is_selectable() { continue; } if !filter(line) { continue; } if line.score > 0 { self.selection = idx; self.make_selection_visible(page_height); return true; } } false } pub fn try_select_next_filtered( &mut self, filter: F, page_height: usize, ) -> bool where F: Fn(&TreeLine) -> bool, { for di in 0..self.lines.len() { let idx = (self.selection + di + 1) % self.lines.len(); let line = &self.lines[idx]; if !line.is_selectable() { continue; } if !filter(line) { continue; } if line.score > 0 { self.selection = idx; self.make_selection_visible(page_height); return true; } } false } pub fn has_dir_missing_sum(&self) -> bool { self.options.needs_sum() && self .lines .iter() .any(|line| line.line_type == TreeLineType::Dir && line.sum.is_none()) } pub fn is_missing_git_status_computation(&self) -> bool { self.git_status.is_not_computed() } /// fetch the file_sums of regular files (thus avoiding the /// long computation which is needed for directories) pub fn fetch_regular_file_sums(&mut self) { for i in 1..self.lines.len() { match self.lines[i].line_type { TreeLineType::Dir | TreeLineType::Pruning => {} _ => { self.lines[i].sum = Some(FileSum::from_file(&self.lines[i].path)); } } } self.sort_siblings(); } /// compute the file_sum of one directory /// /// To compute the size of all of them, this should be called until /// has_dir_missing_sum returns false pub fn fetch_some_missing_dir_sum(&mut self, dam: &Dam, con: &AppContext) { // we prefer to compute the root directory last: its computation // is faster when its first level children are already computed for i in (0..self.lines.len()).rev() { if self.lines[i].sum.is_none() && self.lines[i].line_type == TreeLineType::Dir { self.lines[i].sum = FileSum::from_dir(&self.lines[i].path, dam, con); self.sort_siblings(); return; } } } /// Sort files according to the sort option /// /// (does nothing if it's None) fn sort_siblings(&mut self) { match self.options.sort { Sort::Count => { // we'll try to keep the same path selected let selected_path = self.selected_line().path.to_path_buf(); self.lines[1..].sort_by(|a, b| { let acount = a.sum.map_or(0, |s| s.to_count()); let bcount = b.sum.map_or(0, |s| s.to_count()); bcount.cmp(&acount) }); self.try_select_path(&selected_path); } Sort::Date => { let selected_path = self.selected_line().path.to_path_buf(); self.lines[1..].sort_by(|a, b| { let adate = a.sum.map_or(0, |s| s.to_seconds()); let bdate = b.sum.map_or(0, |s| s.to_seconds()); bdate.cmp(&adate) }); self.try_select_path(&selected_path); } Sort::Size => { let selected_path = self.selected_line().path.to_path_buf(); self.lines[1..].sort_by(|a, b| { let asize = a.sum.map_or(0, |s| s.to_size()); let bsize = b.sum.map_or(0, |s| s.to_size()); bsize.cmp(&asize) }); self.try_select_path(&selected_path); } _ => {} } } /// compute and return the size of the root pub fn total_sum(&self) -> FileSum { if let Some(sum) = self.lines[0].sum { // if the real total sum is computed, it's in the root line sum } else { // if we don't have the sum in root, the nearest estimate is // the sum of sums of lines at depth 1 let mut sum = FileSum::zero(); for i in 1..self.lines.len() { if self.lines[i].depth == 1 { if let Some(line_sum) = self.lines[i].sum { sum += line_sum; } } } sum } } /// Add to the tree the lines which are in the given path but not already in the tree. /// /// Fail if the path is not a descendant of the tree root. fn add_lines_to_path( &mut self, target_path: &Path, con: &AppContext, ) -> Result<(), TreeBuildError> { let mut path = target_path; let mut paths_to_add = Vec::new(); // find the closest parent already in the tree let mut present_ancestor_idx = loop { let idx = self .lines .iter() .position(|line| line.path == path); if let Some(idx) = idx { break idx; } paths_to_add.push(path); let Some(parent) = path.parent() else { warn!("no ancestor in the tree for {:?}", path); return Err(TreeBuildError::NotARootDescendant { path: path.display().to_string(), }); }; path = parent; }; let present_ancestor = &mut self.lines[present_ancestor_idx]; //debug!("present ancestor: {:#?}", &present_ancestor); if present_ancestor.line_type.is_pruning() { info!("unpruning {:?}", &present_ancestor.path); present_ancestor.unprune(); // we should in exchange prune another one ? } debug!("show -> paths to add: {:?}", paths_to_add); if paths_to_add.is_empty() { return Ok(()); } present_ancestor.nb_kept_children += 1; // adding the new lines while let Some(path_to_add) = paths_to_add.pop() { info!("adding {:?}", path_to_add); let new_line_id = self.next_line_id; self.next_line_id += 1; let parent = &self.lines[present_ancestor_idx]; let depth = parent.depth + 1; // The 1 kept_children here might be a trick to avoid the file // being changed to Pruning in the after_lines_changed method... let nb_kept_children = 1; let subpath =path_to_add .strip_prefix(self.root()) .map_err(|_| { // not supposed to happen at this point as we're adding a descendant TreeBuildError::NotARootDescendant { path: path.display().to_string(), } })? .to_string_lossy() .to_string(); let line = TreeLineBuilder { id: new_line_id, path: path_to_add.to_path_buf(), subpath, parent_id: Some(parent.id), depth, unlisted: 0, nb_kept_children, has_error: false, score: 1, direct_match: true, }.build(con)?; present_ancestor_idx = self.lines.len(); self.lines.push(line); } self.after_lines_changed(); Ok(()) } pub fn show_path( &mut self, path: &Path, con: &AppContext, ) -> Result<(), TreeBuildError> { self.add_lines_to_path(path, con)?; let selected = self.try_select_path(path); if !selected { warn!("failed to select {:?}", path); } Ok(()) } } broot-1.46.3/src/tree/tree_line.rs000064400000000000000000000143201046102023000151060ustar 00000000000000use { super::*, crate::{ app::{ AppContext, Selection, SelectionType, }, errors::TreeBuildError, file_sum::FileSum, git::LineGitStatus, }, lazy_regex::regex_captures, std::{ fs, path::{ Path, PathBuf, }, }, }; #[cfg(unix)] use { std::os::unix::fs::MetadataExt, umask::Mode, }; #[cfg(windows)] use is_executable::IsExecutable; pub type TreeLineId = usize; /// a line in the representation of the file hierarchy #[derive(Debug, Clone)] pub struct TreeLine { pub id: TreeLineId, pub parent_id: Option, pub left_branches: Box<[bool]>, // a depth-sized array telling whether a branch pass pub depth: u16, pub path: PathBuf, pub subpath: String, pub icon: Option, pub name: String, // a displayable name - some chars may have been stripped pub line_type: TreeLineType, pub has_error: bool, pub nb_kept_children: usize, pub unlisted: usize, // number of not listed children (Dir) or brothers (Pruning) pub score: i32, // 0 if there's no pattern pub direct_match: bool, pub sum: Option, // None when not measured pub metadata: fs::Metadata, pub git_status: Option, } pub struct TreeLineBuilder { pub path: PathBuf, pub subpath: String, pub id: TreeLineId, pub parent_id: Option, pub depth: u16, pub unlisted: usize, pub nb_kept_children: usize, pub has_error: bool, pub score: i32, pub direct_match: bool, } impl TreeLineBuilder { pub fn build( self, con: &AppContext, ) -> Result { let Self { path, subpath, id, parent_id, depth, unlisted, nb_kept_children, has_error, score, direct_match, } = self; let metadata = fs::symlink_metadata(&path).map_err(|_| TreeBuildError::FileNotFound { path: path.to_string_lossy().to_string(), })?; let line_type = TreeLineType::new(&path, metadata.file_type()); let name = path .file_name() .and_then(|os_str| os_str.to_str()) .unwrap_or("") .replace('\n', ""); let icon = con.icons.as_ref().map(|icon_plugin| { let extension = TreeLine::extension_from_name(&name); let double_extension = extension.and_then(|_| TreeLine::double_extension_from_name(&name)); icon_plugin.get_icon(&line_type, &name, double_extension, extension) }); Ok(TreeLine { id, parent_id, left_branches: vec![false; depth as usize].into_boxed_slice(), depth, icon, name, subpath, path, line_type, has_error, nb_kept_children, unlisted, score, direct_match, sum: None, metadata, git_status: None, }) } } impl TreeLine { pub fn double_extension_from_name(name: &str) -> Option<&str> { regex_captures!(r"\.([^.]+\.[^.]+)", name).map(|(_, de)| de) } pub fn extension_from_name(name: &str) -> Option<&str> { regex_captures!(r"\.([^.]+)$", name).map(|(_, ext)| ext) } pub fn is_selectable(&self) -> bool { !matches!(&self.line_type, TreeLineType::Pruning) } pub fn is_dir(&self) -> bool { match &self.line_type { TreeLineType::Dir => true, TreeLineType::SymLink { final_is_dir, .. } if *final_is_dir => true, _ => false, } } pub fn is_file(&self) -> bool { matches!(&self.line_type, TreeLineType::File) } pub fn is_of( &self, selection_type: SelectionType, ) -> bool { match selection_type { SelectionType::Any => true, SelectionType::File => self.is_file(), SelectionType::Directory => self.is_dir(), } } pub fn extension(&self) -> Option<&str> { Self::extension_from_name(&self.name) } pub fn selection_type(&self) -> SelectionType { use TreeLineType::*; match &self.line_type { File => SelectionType::File, Dir | BrokenSymLink(_) => SelectionType::Directory, SymLink { final_is_dir, .. } => { if *final_is_dir { SelectionType::Directory } else { SelectionType::File } } Pruning => SelectionType::Any, // should not happen today } } pub fn as_selection(&self) -> Selection<'_> { Selection { path: &self.path, stype: self.selection_type(), is_exe: self.is_exe(), line: 0, } } #[cfg(unix)] pub fn mode(&self) -> Mode { Mode::from(self.metadata.mode()) } #[cfg(unix)] pub fn device_id(&self) -> lfs_core::DeviceId { self.metadata.dev().into() } #[cfg(unix)] pub fn mount(&self) -> Option { use crate::filesystems::*; let mut mount_list = MOUNTS.lock().unwrap(); if mount_list.load().is_ok() { mount_list .get_by_device_id(self.metadata.dev().into()) .cloned() } else { None } } pub fn is_exe(&self) -> bool { #[cfg(unix)] return self.mode().is_exe(); #[cfg(windows)] return self.path.is_executable(); } /// build and return the absolute targeted path: either self.path or the /// solved canonicalized symlink pub fn target(&self) -> &Path { match &self.line_type { TreeLineType::SymLink { final_target, .. } => final_target, _ => &self.path, } } pub fn unprune(&mut self) { self.line_type = TreeLineType::new(&self.path, self.metadata.file_type()); self.name = self .path .file_name() .map_or_else(|| "???".to_string(), |n| n.to_string_lossy().to_string()); } } broot-1.46.3/src/tree/tree_line_type.rs000064400000000000000000000056731046102023000161620ustar 00000000000000use { rustc_hash::FxHashSet, std::{ fs, io, path::{Path, PathBuf}, }, }; /// The maximum number of symlink hops before giving up. const MAX_LINK_CHAIN_LENGTH: usize = 128; /// The type of a line which can be displayed as /// part of a tree #[derive(Debug, Clone, PartialEq)] pub enum TreeLineType { File, Dir, BrokenSymLink(String), SymLink { direct_target: String, final_is_dir: bool, final_target: PathBuf, }, Pruning, // a "xxx unlisted" line } pub fn read_link(path: &Path) -> io::Result { let mut target = fs::read_link(path)?; if target.is_relative() { target = path.parent().unwrap().join(&target); } Ok(target) } impl TreeLineType { pub fn is_pruning(&self) -> bool { matches!(self, Self::Pruning) } fn resolve(direct_target: &Path) -> io::Result { let mut final_target = direct_target.to_path_buf(); let mut final_metadata = fs::symlink_metadata(&final_target)?; let mut final_ft = final_metadata.file_type(); let mut final_is_dir = final_ft.is_dir(); let mut link_chain_length = 0; let mut visited = FxHashSet::default(); while final_ft.is_symlink() { final_target = read_link(&final_target)?; if visited.contains(&final_target) { info!( "circular symlink opened by {} and closed by {}", direct_target.display(), final_target.display(), ); return Ok(Self::BrokenSymLink( direct_target.to_string_lossy().into_owned(), )); } visited.insert(final_target.clone()); final_metadata = fs::symlink_metadata(&final_target)?; final_ft = final_metadata.file_type(); final_is_dir = final_ft.is_dir(); link_chain_length += 1; if link_chain_length > MAX_LINK_CHAIN_LENGTH { info!("too long link chain at {}", direct_target.display()); return Ok(Self::BrokenSymLink( direct_target.to_string_lossy().into_owned(), )); } } let direct_target = direct_target.to_string_lossy().into_owned(); Ok(Self::SymLink { direct_target, final_is_dir, final_target, }) } pub fn new(path: &Path, ft: fs::FileType) -> Self { if ft.is_dir() { Self::Dir } else if ft.is_symlink() { if let Ok(direct_target) = read_link(path) { Self::resolve(&direct_target) .unwrap_or_else(|_| { Self::BrokenSymLink(direct_target.to_string_lossy().to_string() )}) } else { Self::BrokenSymLink("???".to_string()) } } else { Self::File } } } broot-1.46.3/src/tree/tree_options.rs000064400000000000000000000212601046102023000156530ustar 00000000000000use { super::Sort, crate::{ cli::Args, conf::Conf, display::{Cols, DEFAULT_COLS}, errors::ConfError, pattern::*, }, clap::Parser, lazy_regex::regex_is_match, std::convert::TryFrom, }; /// Options defining how the tree should be build and|or displayed #[derive(Debug, Clone)] pub struct TreeOptions { pub show_selection_mark: bool, // whether to have a triangle left of selected line pub show_hidden: bool, // whether files whose name starts with a dot should be shown pub only_folders: bool, // whether to hide normal files and links pub show_counts: bool, // whether to show the number of files (> 1 only for dirs) pub show_dates: bool, // whether to show the last modified date pub show_sizes: bool, // whether to show sizes of files and dirs pub max_depth: Option, // the maximum directory depth to recurse to pub show_git_file_info: bool, pub show_device_id: bool, pub show_root_fs: bool, // show information relative to the fs of the root pub trim_root: bool, // whether to cut out direct children of root pub show_permissions: bool, // show classic rwx unix permissions (only on unix) pub respect_git_ignore: bool, // hide files as requested by .gitignore ? pub filter_by_git_status: bool, // only show files whose git status is not nul pub pattern: InputPattern, // an optional filtering/scoring pattern pub date_time_format: &'static str, pub sort: Sort, pub show_tree: bool, // whether to show the tree pub cols_order: Cols, // order of columns pub show_matching_characters_on_path_searches: bool, } impl TreeOptions { /// clone self but without the pattern (if any) pub fn without_pattern(&self) -> Self { TreeOptions { show_selection_mark: self.show_selection_mark, show_hidden: self.show_hidden, only_folders: self.only_folders, max_depth: self.max_depth, show_counts: self.show_counts, show_dates: self.show_dates, show_sizes: self.show_sizes, show_permissions: self.show_permissions, respect_git_ignore: self.respect_git_ignore, filter_by_git_status: self.filter_by_git_status, show_git_file_info: self.show_git_file_info, show_device_id: self.show_device_id, show_root_fs: self.show_root_fs, trim_root: self.trim_root, pattern: InputPattern::none(), date_time_format: self.date_time_format, sort: self.sort, show_tree: self.show_tree, cols_order: self.cols_order, show_matching_characters_on_path_searches: self.show_matching_characters_on_path_searches, } } /// counts must be computed, either for sorting or just for display pub fn needs_counts(&self) -> bool { self.show_counts || self.sort == Sort::Count } /// dates must be computed, either for sorting or just for display pub fn needs_dates(&self) -> bool { self.show_dates || self.sort == Sort::Date } /// sizes must be computed, either for sorting or just for display pub fn needs_sizes(&self) -> bool { self.show_sizes || self.sort == Sort::Size } pub fn needs_sum(&self) -> bool { self.needs_counts() || self.needs_dates() || self.needs_sizes() } /// this method does not exist, you saw nothing /// (at least don't call it other than with the config, once) pub fn set_date_time_format(&mut self, format: String) { self.date_time_format = Box::leak(format.into_boxed_str()); } /// change tree options according to configuration /// (but not the default_flags part, which is handled separately) pub fn apply_config(&mut self, config: &Conf) -> Result<(), ConfError> { if let Some(b) = config.show_selection_mark { self.show_selection_mark = b; } if let Some(format) = &config.date_time_format { self.set_date_time_format(format.clone()); } if let Some(b) = config.show_matching_characters_on_path_searches { self.show_matching_characters_on_path_searches = b; } self.cols_order = config .cols_order .as_ref() .map(Cols::try_from) .transpose()? .unwrap_or(DEFAULT_COLS); Ok(()) } /// apply flags like "sdp" pub fn apply_flags(&mut self, flags: &str) -> Result<(), &'static str> { if !regex_is_match!("^[a-zA-Z]+$", flags) { return Err("Flags must be a sequence of letters"); } let prefixed = format!("-{flags}"); let tokens = vec!["broot", &prefixed]; let args = Args::try_parse_from(tokens) .map_err(|_| { warn!("invalid flags: {:?}", flags); "invalid flag (valid flags are -dDfFgGhHiIpPsSwWtT)" })?; self.apply_launch_args(&args); Ok(()) } /// change tree options according to broot launch arguments pub fn apply_launch_args(&mut self, cli_args: &Args) { if cli_args.sizes { self.show_sizes = true; self.show_root_fs = true; } else if cli_args.no_sizes { self.show_sizes = false; } if cli_args.whale_spotting { self.show_hidden = true; self.respect_git_ignore = false; self.sort = Sort::Size; self.show_sizes = true; self.show_root_fs = true; } if cli_args.no_whale_spotting { self.show_hidden = false; self.respect_git_ignore = true; self.sort = Sort::None; self.show_sizes = false; self.show_root_fs = false; } if cli_args.only_folders { self.only_folders = true; } else if cli_args.no_only_folders { self.only_folders = false; } self.max_depth = cli_args.max_depth; if cli_args.git_status { self.filter_by_git_status = true; self.show_hidden = true; } if cli_args.hidden { self.show_hidden = true; } else if cli_args.no_hidden { self.show_hidden = false; } if cli_args.dates { self.show_dates = true; } else if cli_args.no_dates { self.show_dates = false; } if cli_args.permissions { self.show_permissions = true; } else if cli_args.no_permissions { self.show_permissions = false; } if cli_args.show_root_fs { self.show_root_fs = true; } if cli_args.git_ignored { self.respect_git_ignore = false; } else if cli_args.no_git_ignored { self.respect_git_ignore = true; } if cli_args.show_git_info { self.show_git_file_info = true; } else if cli_args.no_show_git_info { self.show_git_file_info = false; } if cli_args.sort_by_count { self.sort = Sort::Count; self.show_counts = true; } if cli_args.sort_by_date { self.sort = Sort::Date; self.show_dates = true; } if cli_args.sort_by_size { self.sort = Sort::Size; self.show_sizes = true; } if cli_args.tree { self.show_tree = true; } else if cli_args.no_tree { self.show_tree = false; } if cli_args.sort_by_type_dirs_first || cli_args.sort_by_type { self.sort = Sort::TypeDirsFirst; } if cli_args.sort_by_type_dirs_last { self.sort = Sort::TypeDirsLast; } if cli_args.no_sort { self.sort = Sort::None; } if cli_args.trim_root { self.trim_root = true; } else if cli_args.no_trim_root { self.trim_root = false; } } } impl Default for TreeOptions { fn default() -> Self { Self { show_selection_mark: false, show_hidden: false, only_folders: false, max_depth: None, show_counts: false, show_dates: false, show_sizes: false, show_git_file_info: false, show_device_id: false, show_root_fs: false, trim_root: false, show_permissions: false, respect_git_ignore: true, filter_by_git_status: false, pattern: InputPattern::none(), date_time_format: "%Y/%m/%d %R", sort: Sort::None, show_tree: true, cols_order: DEFAULT_COLS, show_matching_characters_on_path_searches: true, } } } broot-1.46.3/src/tree_build/bid.rs000064400000000000000000000013751046102023000150630ustar 00000000000000use { super::bline::BLine, id_arena::Id, std::cmp::Ordering, }; pub type BId = Id; /// a structure making it possible to keep bline references /// sorted in a binary heap with the line with the smallest /// score at the top pub struct SortableBId { pub id: BId, pub score: i32, } impl Eq for SortableBId {} impl PartialEq for SortableBId { fn eq(&self, other: &SortableBId) -> bool { self.score == other.score // unused but required by spec of Ord } } impl Ord for SortableBId { fn cmp(&self, other: &SortableBId) -> Ordering { other.score.cmp(&self.score) } } impl PartialOrd for SortableBId { fn partial_cmp(&self, other: &SortableBId) -> Option { Some(self.cmp(other)) } } broot-1.46.3/src/tree_build/bline.rs000064400000000000000000000100261046102023000154070ustar 00000000000000use { super::bid::BId, crate::{ errors::TreeBuildError, git::IgnoreChain, path::{normalize_path, Directive, SpecialHandling}, tree::*, }, id_arena::Arena, std::{ fs, io, path::PathBuf, result::Result, }, }; /// like a tree line, but with the info needed during the build /// This structure isn't usable independently from the tree builder pub struct BLine { pub parent_id: Option, pub path: PathBuf, pub depth: u16, pub file_type: fs::FileType, pub children: Option>, // sorted and filtered pub next_child_idx: usize, // index for iteration, among the children pub has_error: bool, pub has_match: bool, pub direct_match: bool, pub score: i32, pub nb_kept_children: i32, // used during the trimming step pub git_ignore_chain: IgnoreChain, pub special_handling: SpecialHandling, } impl BLine { pub fn name( &self ) -> &str { self .path .file_name() .and_then(|os_str| os_str.to_str()) .unwrap_or("") } /// a special constructor, checking nothing pub fn from_root( blines: &mut Arena, path: PathBuf, git_ignore_chain: IgnoreChain, _options: &TreeOptions, ) -> Result { if let Ok(md) = fs::metadata(&path) { let file_type = md.file_type(); Ok(blines.alloc(BLine { parent_id: None, path, depth: 0, children: None, next_child_idx: 0, file_type, has_error: false, has_match: true, direct_match: false, score: 0, nb_kept_children: 0, git_ignore_chain, special_handling: Default::default(), })) } else { Err(TreeBuildError::FileNotFound { path: format!("{path:?}"), }) } } /// execute read_dir either on the link if we're a link, /// or on the path otherwise. /// /// Assume the can_enter check has already be done. pub(crate) fn read_dir(&self) -> io::Result { if self.file_type.is_symlink() { if let Ok(target) = fs::read_link(&self.path) { let mut target_path = PathBuf::from(&target); if target_path.is_relative() { target_path = self.path.parent().unwrap().join(target_path); target_path = normalize_path(target_path); } return fs::read_dir(&target_path); } } fs::read_dir(&self.path) } /// tell whether we should list the children of the present line pub fn can_enter(&self) -> bool { if self.file_type.is_dir() && self.special_handling.list != Directive::Never { return true; } if self.special_handling.list == Directive::Always { // we must check we're a link to a directory if self.file_type.is_symlink() { if let Ok(target) = fs::read_link(&self.path) { let mut target_path = PathBuf::from(&target); if target_path.is_relative() { target_path = self.path.parent().unwrap().join(target_path); } if let Ok(target_metadata) = fs::symlink_metadata(&target_path) { if target_metadata.file_type().is_dir() { if self.path.starts_with(target_path) { debug!("not entering link because it's a parent"); // lets's not cycle } else { debug!("entering {:?} because of special path rule", &self.path); return true; } } } } } } false } } broot-1.46.3/src/tree_build/build_report.rs000064400000000000000000000007561046102023000170210ustar 00000000000000 /// Information from the builder about the /// tree operation /// /// A file is counted at most once here #[derive(Debug, Clone, Copy, Default)] pub struct BuildReport { /// number of times a gitignore pattern excluded a file pub gitignored_count: usize, /// number of times a file was excluded because hidden /// (this count stays at zero if hidden files are displayed) pub hidden_count: usize, /// number of errors excluding a file pub error_count: usize, } broot-1.46.3/src/tree_build/builder.rs000064400000000000000000000501471046102023000157540ustar 00000000000000use { super::{ bid::{ BId, SortableBId, }, bline::BLine, BuildReport, }, crate::{ app::AppContext, errors::TreeBuildError, git::{ IgnoreChain, Ignorer, LineStatusComputer, }, path::Directive, pattern::Candidate, task_sync::{ ComputationResult, Dam, }, tree::*, }, git2::Repository, id_arena::Arena, std::{ collections::{ BinaryHeap, VecDeque, }, fs, path::PathBuf, result::Result, time::{ Duration, Instant, }, }, }; #[cfg(unix)] use std::os::unix::ffi::OsStrExt; #[cfg(target_os = "windows")] use std::ffi::OsStr; #[cfg(target_os = "windows")] trait OsStrWin { fn as_bytes(&self) -> &[u8]; } #[cfg(target_os = "windows")] impl OsStrWin for OsStr { fn as_bytes(&self) -> &[u8] { static INVALID_UTF8: &[u8] = b"invalid utf8"; self.to_str().map(|s| s.as_bytes()).unwrap_or(INVALID_UTF8) } } /// If a search found enough results to fill the screen but didn't scan /// everything, we search a little more in case we find better matches /// but not after the NOT_LONG duration. static NOT_LONG: Duration = Duration::from_millis(900); /// The TreeBuilder builds a Tree according to options (including an optional search pattern) /// Instead of the final TreeLine, the builder uses an internal structure: BLine. /// All BLines used during build are stored in the blines arena and kept until the end. /// Most operations and temporary data structures just deal with the ids of lines /// the blines arena. pub struct TreeBuilder<'c> { pub options: TreeOptions, targeted_size: usize, // the number of lines we should fill (height of the screen) blines: Arena, root_id: BId, subpath_offset: usize, total_search: bool, git_ignorer: Ignorer, line_status_computer: Option, con: &'c AppContext, pub matches_max: Option, // optional hard limit trim_root: bool, pub deep: bool, report: BuildReport, } impl<'c> TreeBuilder<'c> { pub fn from( path: PathBuf, options: TreeOptions, targeted_size: usize, con: &'c AppContext, ) -> Result, TreeBuildError> { let mut blines = Arena::new(); let subpath_offset = path.components().count(); let mut git_ignorer = time!(Ignorer::default()); let root_ignore_chain = git_ignorer.root_chain(&path); let line_status_computer = if options.filter_by_git_status || options.show_git_file_info { time!( "init line_status_computer", Repository::discover(&path) .ok() .and_then(LineStatusComputer::from), ) } else { None }; let root_id = BLine::from_root(&mut blines, path, root_ignore_chain, &options)?; let trim_root = match ( options.trim_root, options.pattern.is_some(), options.sort.prevent_deep_display(), ) { // we never want to trim the root if there's a sort (_, _, true) => false, // if the user don't want root trimming, we don't trim (false, _, _) => false, // if there's a pattern, we try to show at least root matches (_, true, _) => false, // in other cases, as the user wants trimming, we trim _ => true, }; Ok(TreeBuilder { options, targeted_size, blines, root_id, subpath_offset, total_search: true, // we'll set it to false if we don't look at all children git_ignorer, line_status_computer, con, trim_root, matches_max: None, deep: true, report: BuildReport::default(), }) } /// Return a bline if the dir_entry directly matches the options and there's no error fn make_bline( &mut self, parent_id: BId, e: &fs::DirEntry, depth: u16, ) -> Option { let name = e.file_name(); if name.is_empty() { // this should not really happen as the only path with an empty name is the root // and we don't call this function for the tree root self.report.error_count += 1; return None; } let path = e.path(); let special_handling = self.con.special_paths.find(&path); if special_handling.show == Directive::Never { return None; } if !self.options.show_hidden && name.as_bytes()[0] == b'.' && special_handling.show != Directive::Always { self.report.hidden_count += 1; return None; } let Some(name) = name.to_str() else { warn!("invalid utf8 file name: {:?}", name); self.report.error_count += 1; return None; }; let mut has_match = true; let mut score = 10000 - i32::from(depth); // we dope less deep entries let file_type = match e.file_type() { Ok(ft) => ft, Err(_) => { self.report.error_count += 1; return None; } }; let subpath = path .components() .skip(self.subpath_offset) .collect::(); let candidate = Candidate { name, subpath: &subpath.to_string_lossy(), path: &path, regular_file: file_type.is_file(), }; let direct_match = if let Some(pattern_score) = self.options.pattern.pattern.score_of(candidate) { // we dope direct matches to compensate for depth doping of parent folders score += pattern_score + 10; true } else { has_match = false; false }; if has_match && self.options.filter_by_git_status { if let Some(line_status_computer) = &self.line_status_computer { if !line_status_computer.is_interesting(&path) { has_match = false; } } } if file_type.is_file() && !has_match { return None; } if self.options.only_folders && !file_type.is_dir() { if !file_type.is_symlink() { return None; } let Ok(target_metadata) = fs::metadata(&path) else { return None; }; if !target_metadata.is_dir() { return None; } } if self.options.respect_git_ignore { let parent_chain = &self.blines[parent_id].git_ignore_chain; if !self .git_ignorer .accepts(parent_chain, &path, name, file_type.is_dir()) { if special_handling.show != Directive::Always { return None; } } }; Some(BLine { parent_id: Some(parent_id), path, depth, file_type, children: None, next_child_idx: 0, has_error: false, has_match, direct_match, score, nb_kept_children: 0, git_ignore_chain: IgnoreChain::default(), special_handling, }) } /// Fill the bline's children vec of blines. /// /// Return true when there are direct matches among children fn load_children( &mut self, bid: BId, ) -> bool { let mut has_child_match = false; match self.blines[bid].read_dir() { Ok(entries) => { let mut children: Vec = Vec::new(); let child_depth = self.blines[bid].depth + 1; let mut lines = Vec::new(); for e in entries.flatten() { if let Some(line) = self.make_bline(bid, &e, child_depth) { lines.push(line); } } for mut bl in lines { if self.options.respect_git_ignore { let parent_chain = &self.blines[bid].git_ignore_chain; bl.git_ignore_chain = if bl.file_type.is_dir() { self.git_ignorer.deeper_chain(parent_chain, &bl.path) } else { parent_chain.clone() }; } if bl.has_match { self.blines[bid].has_match = true; has_child_match = true; } let child_id = self.blines.alloc(bl); children.push(child_id); } children.sort_by(|&a, &b| { self.blines[a] .name() .to_lowercase() .cmp(&self.blines[b].name().to_lowercase()) }); self.blines[bid].children = Some(children); } Err(_err) => { self.blines[bid].has_error = true; self.blines[bid].children = Some(Vec::new()); } } has_child_match } /// return the next child. /// load_children must have been called before on parent_id fn next_child( &mut self, parent_id: BId, ) -> Option { let bline = &mut self.blines[parent_id]; if let Some(children) = &bline.children { if bline.next_child_idx < children.len() { let next_child = children[bline.next_child_idx]; bline.next_child_idx += 1; Some(next_child) } else { Option::None } } else { unreachable!(); } } /// first step of the build: we explore the directories and gather lines. /// If there's no search pattern we stop when we have enough lines to fill the screen. /// If there's a pattern, we try to gather more lines that will be sorted afterwards. fn gather_lines( &mut self, total_search: bool, dam: &Dam, ) -> Result, TreeBuildError> { let start = Instant::now(); let mut out_blines: Vec = Vec::new(); // the blines we want to display let optimal_size = if self.options.pattern.pattern.has_real_scores() { 10 * self.targeted_size } else { self.targeted_size }; out_blines.push(self.root_id); let mut nb_lines_ok = 1; // in out_blines let mut open_dirs: VecDeque = VecDeque::new(); let mut next_level_dirs: Vec = Vec::new(); self.load_children(self.root_id); open_dirs.push_back(self.root_id); let deep = self.deep && self.options.show_tree && !self.options.sort.prevent_deep_display(); loop { if !total_search && ((nb_lines_ok > optimal_size) || (nb_lines_ok >= self.targeted_size && start.elapsed() > NOT_LONG)) { self.total_search = false; break; } if let Some(max) = self.matches_max { if nb_lines_ok > max { return Err(TreeBuildError::TooManyMatches { max }); } } if let Some(open_dir_id) = open_dirs.pop_front() { if let Some(child_id) = self.next_child(open_dir_id) { open_dirs.push_back(open_dir_id); let child = &self.blines[child_id]; if child.has_match { nb_lines_ok += 1; } if self.options.max_depth.map_or(false, |max| child.depth > max) { break; } if child.can_enter() { next_level_dirs.push(child_id); } out_blines.push(child_id); } } else { // this depth is finished, we must go deeper if !deep { break; } if next_level_dirs.is_empty() { // except there's nothing deeper break; } for next_level_dir_id in &next_level_dirs { if dam.has_event() { info!("task expired (core build - inner loop)"); return Err(TreeBuildError::Interrupted); } let has_child_match = self.load_children(*next_level_dir_id); if has_child_match { // we must ensure the ancestors are made Ok let mut id = *next_level_dir_id; loop { let bline = &mut self.blines[id]; if !bline.has_match { bline.has_match = true; nb_lines_ok += 1; } if let Some(pid) = bline.parent_id { id = pid; } else { break; } } } open_dirs.push_back(*next_level_dir_id); } next_level_dirs.clear(); } } if let Some(max) = self.matches_max { if nb_lines_ok > max { return Err(TreeBuildError::TooManyMatches { max }); } } if !self.trim_root { // if the root directory isn't totally read, we finished it even // it it goes past the bottom of the screen while let Some(child_id) = self.next_child(self.root_id) { out_blines.push(child_id); } } Ok(out_blines) } /// Post search trimming /// When there's a pattern, gathering normally brings many more lines than /// strictly necessary to fill the screen. /// This function keeps only the best ones while taking care of not /// removing a parent before its children. fn trim_excess( &mut self, out_blines: &[BId], ) { let mut count = 1; for id in &out_blines[1..] { if self.blines[*id].has_match { //debug!("bline before trimming: {:?}", &self.blines[*idx].path); count += 1; let parent_id = self.blines[*id].parent_id.unwrap(); // (we can unwrap because only the root can have a None parent) self.blines[parent_id].nb_kept_children += 1; } } let mut remove_queue: BinaryHeap = BinaryHeap::new(); for id in &out_blines[1..] { let bline = &self.blines[*id]; if bline.has_match && bline.nb_kept_children == 0 && (bline.depth > 1 || self.trim_root) { remove_queue.push(SortableBId { id: *id, score: bline.score, }); } } while count > self.targeted_size { if let Some(sli) = remove_queue.pop() { self.blines[sli.id].has_match = false; let parent_id = self.blines[sli.id].parent_id.unwrap(); let parent = &mut self.blines[parent_id]; parent.nb_kept_children -= 1; parent.next_child_idx -= 1; // to fix the number of "unlisted" if parent.nb_kept_children == 0 { remove_queue.push(SortableBId { id: parent_id, score: parent.score, }); } count -= 1; } else { debug!("trimming prematurely interrupted"); break; } } } fn make_tree_line( &self, bid: BId, ) -> Result { let bline = &self.blines[bid]; let path = bline.path.clone(); let subpath = if bline.depth == 0 { bline.path.to_string_lossy().to_string() } else { bline .path .components() .skip(self.subpath_offset) .map(|c| c.as_os_str()) .collect::() .to_string_lossy() .to_string() }; let unlisted = bline .children .as_ref() .map_or(0, |children| children.len() - bline.next_child_idx); TreeLineBuilder { path, subpath, id: bid.index(), parent_id: bline.parent_id.map(|bid| bid.index()), depth: bline.depth, unlisted, nb_kept_children: bline.nb_kept_children as usize, has_error: bline.has_error, score: bline.score, direct_match: bline.direct_match, } .build(self.con) } /// make a tree from the builder's specific structure fn take_as_tree( mut self, out_blines: &[BId], ) -> Tree { let mut lines: Vec = Vec::new(); for id in out_blines { if self.blines[*id].has_match { // we need to count the children, so we load them if self.blines[*id].can_enter() && self.blines[*id].children.is_none() { self.load_children(*id); } if let Ok(tree_line) = self.make_tree_line(*id) { lines.push(tree_line); } else { // I guess the file went missing during tree computation warn!( "Error while builind treeline for {:?}", self.blines[*id].path, ); } } } let next_line_id = lines.iter().map(|line| line.id).max().unwrap_or(0) + 1; let mut tree = Tree { lines, next_line_id, selection: 0, options: self.options.clone(), scroll: 0, total_search: self.total_search, git_status: ComputationResult::None, build_report: self.report, }; tree.after_lines_changed(); if let Some(computer) = self.line_status_computer { // tree git status is slow to compute, we just mark it should be // done (later on) tree.git_status = ComputationResult::NotComputed; // it would make no sense to keep only files having a git status and // not display that type for line in &mut tree.lines { line.git_status = computer.line_status(&line.path); } } tree } /// build a tree. Can be called only once per builder. /// /// Return None if the lifetime expires before end of computation /// (usually because the user hit a key) pub fn build_tree( mut self, total_search: bool, dam: &Dam, ) -> Result { let blines_ids = self.gather_lines(total_search, dam)?; debug!("blines before trimming: {}", blines_ids.len()); if !self.total_search { self.trim_excess(&blines_ids); } Ok(self.take_as_tree(&blines_ids)) } pub fn build_paths( mut self, total_search: bool, dam: &Dam, filter: F, ) -> Result, TreeBuildError> where F: Fn(&BLine) -> bool, { self.gather_lines(total_search, dam).map(|mut blines_ids| { blines_ids .drain(..) .filter(|&bid| self.blines[bid].direct_match && filter(&self.blines[bid])) .map(|id| self.blines[id].path.clone()) .collect() }) } } broot-1.46.3/src/tree_build/mod.rs000064400000000000000000000002101046102023000150670ustar 00000000000000mod bid; mod bline; mod build_report; mod builder; pub use { bid::BId, builder::TreeBuilder, build_report::BuildReport, }; broot-1.46.3/src/verb/arg_def.rs000064400000000000000000000005271046102023000145320ustar 00000000000000use { crate::{ app::SelectionType, path::PathAnchor, }, }; /// The definition of an argument given to a verb /// as understood from the invocation pattern #[derive(Debug, Clone, Copy)] pub enum ArgDef { Path { anchor: PathAnchor, selection_type: SelectionType, }, Theme, Unspecified, } broot-1.46.3/src/verb/exec_pattern.rs000064400000000000000000000077211046102023000156270ustar 00000000000000use { crate::{ path::*, verb::*, }, serde::{Deserialize, Serialize}, std::{ path::Path, fmt, }, }; /// A pattern which can be expanded into an executable #[derive(Debug, Clone, Deserialize, Serialize)] #[serde(untagged)] pub enum ExecPattern { String(String), Array(Vec), } impl ExecPattern { pub fn is_empty(&self) -> bool { match self { Self::String(s) => s.is_empty(), Self::Array(v) => v.is_empty(), } } pub fn has_selection_group(&self) -> bool { match self { Self::String(s) => str_has_selection_group(s), Self::Array(v) => v.iter().any(|s| str_has_selection_group(s)), } } pub fn has_other_panel_group(&self) -> bool { match self { Self::String(s) => str_has_other_panel_group(s), Self::Array(v) => v.iter().any(|s| str_has_other_panel_group(s)), } } pub fn as_internal_pattern(&self) -> Option<&str> { match self { Self::String(s) => { if s.starts_with(':') || s.starts_with(' ') { Some(&s[1..]) } else { None } } Self::Array(_) => None, } } pub fn into_array(self) -> Vec { match self { Self::String(s) => { splitty::split_unquoted_whitespace(&s) .unwrap_quotes(true) .map(|s| s.to_string()) .collect() } Self::Array(v) => v, } } pub fn from_string>(t: T) -> Self { Self::String(t.into()) } pub fn from_array(v: Vec) -> Self { Self::Array(v) } pub fn tokenize(self) -> Self { Self::Array(self.into_array()) } pub fn apply(&self, f: &dyn Fn(&str) -> String) -> Self { Self::Array( match self { Self::String(s) => { splitty::split_unquoted_whitespace(s) .unwrap_quotes(true) .map(f) .collect() } Self::Array(v) => { v.iter() .map(|s| f(s)) .collect() } } ) } pub fn fix_paths(self) -> Self { match self { Self::String(s) => Self::Array( splitty::split_unquoted_whitespace(&s) .unwrap_quotes(true) .map(fix_token_path) .collect() ), Self::Array(v) => Self::Array( v.iter() .map(fix_token_path) .collect() ), } } } fn fix_token_path + AsRef>(token: T) -> String { //let s = token.as_ref(); let path = Path::new(token.as_ref()); if path.exists() { if let Some(path) = path.to_str() { return path.to_string(); } } else if TILDE_REGEX.is_match(token.as_ref()) { let path = untilde(token.as_ref()); if path.exists() { if let Some(path) = path.to_str() { return path.to_string(); } } } token.into() } // this implementation builds a string usable for exect impl fmt::Display for ExecPattern { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::String(s) => s.fmt(f), Self::Array(v) => { for (idx, s) in v.iter().enumerate() { if idx > 0 { write!(f, " ")?; } if s.contains(' ') { write!(f, "\"{s}\"")?; } else { write!(f, "{s}")?; } } Ok(()) } } } } broot-1.46.3/src/verb/execution_builder.rs000064400000000000000000000421571046102023000166610ustar 00000000000000use { super::*, crate::{ app::*, command::*, path, }, rustc_hash::FxHashMap, regex::Captures, std::path::{Path, PathBuf}, }; /// a temporary structure gathering selection and invocation /// parameters and able to generate an executable string from /// a verb's execution pattern pub struct ExecutionStringBuilder<'b> { /// the current file selection pub sel_info: SelInfo<'b>, /// the current root of the app root: &'b Path, /// the selection in the other panel, when there are exactly two other_file: Option<&'b PathBuf>, /// parsed arguments invocation_values: Option>, /// whether to keep groups which can't be solved or remove them keep_groups: bool, } impl<'b> ExecutionStringBuilder<'b> { /// constructor to use when there's no invocation string /// (because we're in the process of building one, for example /// when a verb is triggered from a key shortcut) pub fn without_invocation( sel_info: SelInfo<'b>, app_state: &'b AppState, ) -> Self { Self { sel_info, root: &app_state.root, other_file: app_state.other_panel_path.as_ref(), invocation_values: None, keep_groups: false, } } pub fn with_invocation( invocation_parser: Option<&InvocationParser>, sel_info: SelInfo<'b>, app_state: &'b AppState, invocation_args: Option<&String>, ) -> Self { let invocation_values = invocation_parser .as_ref() .zip(invocation_args.as_ref()) .and_then(|(parser, args)| parser.parse(args)); Self { sel_info, root: &app_state.root, other_file: app_state.other_panel_path.as_ref(), invocation_values, keep_groups: false, } } fn get_raw_replacement( &self, f: F ) -> Option where F: Fn(Option>) -> Option { match self.sel_info { SelInfo::None => f(None), SelInfo::One(sel) => f(Some(sel)), SelInfo::More(stage) => { let mut sels = stage.paths().iter() .map(|path| Selection { path, line: 0, stype: SelectionType::from(path), is_exe: false, }); f(sels.next()) .filter(|first_rcr| { for sel in sels { let rcr = f(Some(sel)); if rcr.as_ref() != Some(first_rcr) { return false; } } true }) } } } fn get_raw_capture_replacement( &self, ec: &Captures<'_>, con: &AppContext, ) -> Option { self.get_raw_replacement(|sel| { self.get_raw_sel_capture_replacement(ec, sel, con) }) } /// return the standard replacement (ie not one from the invocation) fn get_raw_sel_name_standard_replacement( &self, name: &str, sel: Option>, con: &AppContext, ) -> Option { debug!("repl name : {:?}", name); match name { "root" => Some(path_to_string(self.root)), "initial-root" => Some(path_to_string(&con.initial_root)), "line" => sel.map(|s| s.line.to_string()), "file" => sel.map(|s| s.path) .map(path_to_string), "file-name" => sel.map(|s| s.path) .and_then(|path| path.file_name()) .and_then(|oss| oss.to_str()) .map(|s| s.to_string()), "file-stem" => sel.map(|s| s.path) .and_then(|path| path.file_stem()) .and_then(|oss| oss.to_str()) .map(|s| s.to_string()), "file-extension" => { debug!("expending file extension"); sel.map(|s| s.path) .and_then(|path| path.extension()) .and_then(|oss| oss.to_str()) .map(|s| s.to_string()) } "file-dot-extension" => { debug!("expending file dot extension"); sel.map(|s| s.path) .and_then(|path| path.extension()) .and_then(|oss| oss.to_str()) .map(|ext| format!(".{ext}")) .or_else(|| Some("".to_string())) } "directory" => sel.map(|s| path::closest_dir(s.path)) .map(path_to_string), "parent" => sel.and_then(|s| s.path.parent()) .map(path_to_string), "other-panel-file" => self.other_file .map(path_to_string), "other-panel-filename" => self.other_file .and_then(|path| path.file_name()) .and_then(|oss| oss.to_str()) .map(|s| s.to_string()), "other-panel-directory" => self .other_file .map(|p| path::closest_dir(p)) .as_ref() .map(path_to_string), "other-panel-parent" => self .other_file .and_then(|p| p.parent()) .map(path_to_string), "git-root" => { // path to git repo workdir debug!("finding git root"); sel .and_then(|s| git2::Repository::discover(s.path).ok()) .and_then(|repo| repo.workdir().map(path_to_string)) } "git-name" => { // name of the git repo workdir sel .and_then(|s| git2::Repository::discover(s.path).ok()) .and_then(|repo| repo.workdir().and_then(|path| { path.file_name() .and_then(|oss| oss.to_str()) .map(|s| s.to_string()) })) } "file-git-relative" => { // file path relative to git repo workdir let sel = sel?; let path = git2::Repository::discover(self.root).ok() .and_then(|repo| repo.workdir().map(path_to_string)) .and_then(|gitroot| sel.path.strip_prefix(gitroot).ok()) .filter(|p| { // it's empty when the file is both the tree root and the git root !p.as_os_str().is_empty() }) .unwrap_or(sel.path); Some(path_to_string(path)) } _ => None, } } fn get_raw_sel_capture_replacement( &self, ec: &Captures<'_>, sel: Option>, con: &AppContext, ) -> Option { let name = ec.get(1).unwrap().as_str(); self.get_raw_sel_name_standard_replacement(name, sel, con) .or_else(||{ // it's not one of the standard group names, so we'll look // into the ones provided by the invocation pattern self.invocation_values.as_ref() .and_then(|map| map.get(name)) .and_then(|value| { if let Some(fmt) = ec.get(2) { match fmt.as_str() { "path-from-directory" => { sel.map(|s| path::closest_dir(s.path)) .map(|dir| path::path_str_from(dir, value)) } "path-from-parent" => { sel.and_then(|s| s.path.parent()) .map(|dir| path::path_str_from(dir, value)) } _ => Some(format!("invalid format: {:?}", fmt.as_str())), } } else { Some(value.to_string()) } }) }) } #[inline] fn get_capture_replacement( &self, ec: &Captures<'_>, con: &AppContext, ) -> String { self.get_raw_capture_replacement(ec, con) .unwrap_or_else(|| if self.keep_groups { ec[0].to_string() } else { "".to_string() } ) } fn get_sel_capture_replacement( &self, ec: &Captures<'_>, sel: Option>, con: &AppContext, ) -> String { self.get_raw_sel_capture_replacement(ec, sel, con) .unwrap_or_else(|| if self.keep_groups { ec[0].to_string() } else { "".to_string() } ) } /// fills groups having a default value (after the colon) /// /// This is used to fill the input in case on non auto_exec /// verb triggered with a key pub fn invocation_with_default( &self, verb_invocation: &VerbInvocation, con: &AppContext, ) -> VerbInvocation { VerbInvocation { name: verb_invocation.name.clone(), args: verb_invocation.args.as_ref().map(|a| { GROUP.replace_all( a.as_str(), |ec: &Captures<'_>| { ec.get(2) .map(|default_name| default_name.as_str()) .and_then(|default_name| self.get_raw_replacement(|sel| self.get_raw_sel_name_standard_replacement(default_name, sel, con) ) ) .unwrap_or_default() }, ).to_string() }), bang: verb_invocation.bang, } } fn base_dir(&self) -> &Path { self.sel_info .one_sel() .map_or(self.root, |sel| sel.path) } /// replace groups in a sequence /// /// Replacing escapes for the shell for externals, and without /// escaping for internals. /// /// Note that this is *before* asking the (local or remote) panel /// state the sequential execution of the different commands. In /// this secondary execution, new replacements are expected too, /// depending on the verbs. pub fn sequence( &self, sequence: &Sequence, verb_store: &VerbStore, con: &AppContext, panel_state_type: Option, ) -> Sequence { let mut inputs = Vec::new(); for input in sequence.raw.split(&sequence.separator) { let raw_parts = CommandParts::from(input.to_string()); let (_, verb_invocation) = raw_parts.split(); let verb_is_external = verb_invocation .and_then(|vi| { let command = Command::from_parts(vi, true); if let Command::VerbInvocate(invocation) = &command { let search = verb_store.search_prefix(&invocation.name, panel_state_type); if let PrefixSearchResult::Match(_, verb) = search { return Some(verb); } } None }) .map_or(false, |verb| verb.get_internal().is_none()); let input = if verb_is_external { self.shell_exec_string(&ExecPattern::from_string(input), con) } else { self.string(input, con) }; inputs.push(input); } Sequence { raw: inputs.join(&sequence.separator), separator: sequence.separator.clone(), } } /// build a raw string, without escapings pub fn string( &self, pattern: &str, con: &AppContext, ) -> String { GROUP .replace_all( pattern, |ec: &Captures<'_>| self.get_capture_replacement(ec, con), ) .to_string() } /// build a path pub fn path( &self, pattern: &str, con: &AppContext, ) -> PathBuf { path::path_from( self.base_dir(), path::PathAnchor::Unspecified, &GROUP.replace_all( pattern, |ec: &Captures<'_>| self.get_capture_replacement(ec, con), ) ) } /// build a shell compatible command, with escapings pub fn shell_exec_string( &self, exec_pattern: &ExecPattern, con: &AppContext, ) -> String { exec_pattern .apply(&|s| { GROUP.replace_all( s, |ec: &Captures<'_>| self.get_capture_replacement(ec, con), ).to_string() }) .fix_paths() .to_string() } /// build a shell compatible command, with escapings, for a specific /// selection (this is intended for execution on all selections of a /// stage) pub fn sel_shell_exec_string( &self, exec_pattern: &ExecPattern, sel: Option>, con: &AppContext, ) -> String { exec_pattern .apply(&|s| { GROUP.replace_all( s, |ec: &Captures<'_>| self.get_sel_capture_replacement(ec, sel, con), ).to_string() }) .fix_paths() .to_string() } /// build a vec of tokens which can be passed to Command to /// launch an executable pub fn exec_token( &self, exec_pattern: &ExecPattern, con: &AppContext, ) -> Vec { exec_pattern .apply(&|s| { GROUP.replace_all( s, |ec: &Captures<'_>| self.get_capture_replacement(ec, con), ).to_string() }) .fix_paths() .into_array() } /// build a vec of tokens which can be passed to Command to /// launch an executable pub fn sel_exec_token( &self, exec_pattern: &ExecPattern, sel: Option>, con: &AppContext, ) -> Vec { exec_pattern .apply(&|s| { GROUP.replace_all( s, |ec: &Captures<'_>| self.get_sel_capture_replacement(ec, sel, con), ).to_string() }) .fix_paths() .into_array() } } #[cfg(test)] mod execution_builder_test { // allows writing vo!["a", "b"] to build a vec of strings macro_rules! vo { ($($item:literal),* $(,)?) => {{ let mut vec = Vec::new(); $( vec.push($item.to_owned()); )* vec }} } use { super::*, crate::{ stage::*, }, }; fn check_build_execution_from_sel( exec_patterns: Vec, path: &str, replacements: Vec<(&str, &str)>, chk_exec_token: Vec<&str>, ) { let path = PathBuf::from(path); let sel = Selection { path: &path, line: 0, stype: SelectionType::File, is_exe: false, }; let app_state = AppState { stage: Stage::default(), root: PathBuf::from("/".to_owned()), other_panel_path: None, }; let mut builder = ExecutionStringBuilder::without_invocation( SelInfo::One(sel), &app_state, ); let mut map = FxHashMap::default(); for (k, v) in replacements { map.insert(k.to_owned(), v.to_owned()); } builder.invocation_values = Some(map); let con = AppContext::default(); for exec_pattern in exec_patterns { let exec_token = builder.exec_token(&exec_pattern, &con); assert_eq!(exec_token, chk_exec_token); } } #[test] fn test_build_execution() { check_build_execution_from_sel( vec![ExecPattern::from_string("vi {file}")], "/home/dys/dev", vec![], vec!["vi", "/home/dys/dev"], ); check_build_execution_from_sel( vec![ ExecPattern::from_string("/bin/e.exe -a {arg} -e {file}"), ExecPattern::from_array(vo!["/bin/e.exe","-a", "{arg}", "-e", "{file}"]), ], "expérimental & 试验性", vec![("arg", "deux mots")], vec!["/bin/e.exe", "-a", "deux mots", "-e", "expérimental & 试验性"], ); check_build_execution_from_sel( vec![ ExecPattern::from_string("xterm -e \"kak {file}\""), ExecPattern::from_array(vo!["xterm", "-e", "kak {file}"]), ], "/path/to/file", vec![], vec!["xterm", "-e", "kak /path/to/file"], ); } } fn path_to_string>(path: P) -> String { path.as_ref().to_string_lossy().to_string() } broot-1.46.3/src/verb/external_execution.rs000064400000000000000000000160221046102023000170450ustar 00000000000000use { super::*, crate::{ app::*, display::W, errors::ProgramError, launchable::Launchable, }, std::{ fs::OpenOptions, io::Write, path::PathBuf, }, }; /// Definition of how the user input should be interpreted /// to be executed in an external command. #[derive(Debug, Clone)] pub struct ExternalExecution { /// the pattern which will result in an executable string when /// completed with the args. /// This pattern may include names coming from the invocation /// pattern (like {my-arg}) and special names automatically filled by /// broot from the selection and application state: /// * {file} /// * {directory} /// * {parent} /// * {other-panel-file} /// * {other-panel-directory} /// * {other-panel-parent} pub exec_pattern: ExecPattern, /// how the external process must be launched pub exec_mode: ExternalExecutionMode, /// the working directory of the new process, or none if we don't /// want to set it pub working_dir: Option, /// whether we need to switch to the normal terminal for /// the duration of the execution of the process pub switch_terminal: bool, /// whether the tree must be refreshed after the verb is executed pub refresh_after: bool, } impl ExternalExecution { pub fn new( exec_pattern: ExecPattern, exec_mode: ExternalExecutionMode, ) -> Self { Self { exec_pattern, exec_mode, working_dir: None, switch_terminal: true, // by default we switch refresh_after: true, // by default we refresh } } pub fn with_working_dir(mut self, b: Option) -> Self { self.working_dir = b; self } /// goes from the external execution command to the CmdResult: /// - by executing the command if it can be executed from a subprocess /// - by building a command to be executed in parent shell in other cases pub fn to_cmd_result( &self, w: &mut W, builder: ExecutionStringBuilder<'_>, con: &AppContext, ) -> Result { match self.exec_mode { ExternalExecutionMode::FromParentShell => self.cmd_result_exec_from_parent_shell( builder, con, ), ExternalExecutionMode::LeaveBroot => self.cmd_result_exec_leave_broot( builder, con, ), ExternalExecutionMode::StayInBroot => self.cmd_result_exec_stay_in_broot( w, builder, con, ), } } fn working_dir_path( &self, builder: &ExecutionStringBuilder<'_>, con: &AppContext, ) -> Option { self.working_dir .as_ref() .map(|pattern| builder.path(pattern, con)) .filter(|pb| { if pb.exists() { true } else { warn!("workding dir doesn't exist: {:?}", pb); false } }) } /// build the cmd result as an executable which will be called /// from the parent shell (meaning broot must quit) fn cmd_result_exec_from_parent_shell( &self, builder: ExecutionStringBuilder<'_>, con: &AppContext, ) -> Result { if builder.sel_info.count_paths() > 1 { return Ok(CmdResult::error( "only verbs returning to broot on end can be executed on a multi-selection" )); } if let Some(ref export_path) = con.launch_args.outcmd { // Broot was probably launched as br. // the whole command is exported in the passed file let f = OpenOptions::new().append(true).open(export_path)?; writeln!(&f, "{}", builder.shell_exec_string(&self.exec_pattern, con))?; Ok(CmdResult::Quit) } else { Ok(CmdResult::error( "this verb needs broot to be launched as `br`. Try `broot --install` if necessary." )) } } /// build the cmd result as an executable which will be called in a process /// launched by broot at end of broot fn cmd_result_exec_leave_broot( &self, builder: ExecutionStringBuilder<'_>, con: &AppContext, ) -> Result { if builder.sel_info.count_paths() > 1 { return Ok(CmdResult::error( "only verbs returning to broot on end can be executed on a multi-selection" )); } let launchable = Launchable::program( builder.exec_token(&self.exec_pattern, con), self.working_dir_path(&builder, con), self.switch_terminal, con, )?; Ok(CmdResult::from(launchable)) } /// build the cmd result as an executable which will be called in a process /// launched by broot fn cmd_result_exec_stay_in_broot( &self, w: &mut W, builder: ExecutionStringBuilder<'_>, con: &AppContext, ) -> Result { let working_dir_path = self.working_dir_path(&builder, con); match &builder.sel_info { SelInfo::None | SelInfo::One(_) => { // zero or one selection -> only one execution let launchable = Launchable::program( builder.exec_token(&self.exec_pattern, con), working_dir_path, self.switch_terminal, con, )?; info!("Executing not leaving, launchable {:?}", launchable); if let Err(e) = launchable.execute(Some(w)) { warn!("launchable failed : {:?}", e); return Ok(CmdResult::error(e.to_string())); } } SelInfo::More(stage) => { // multiselection -> we must execute on all paths let sels = stage.paths().iter() .map(|path| Selection { path, line: 0, stype: SelectionType::from(path), is_exe: false, }); for sel in sels { let launchable = Launchable::program( builder.sel_exec_token(&self.exec_pattern, Some(sel), con), working_dir_path.clone(), self.switch_terminal, con, )?; if let Err(e) = launchable.execute(Some(w)) { warn!("launchable failed : {:?}", e); return Ok(CmdResult::error(e.to_string())); } } } } if self.refresh_after { Ok(CmdResult::RefreshState { clear_cache: true }) } else { Ok(CmdResult::Keep) } } } broot-1.46.3/src/verb/external_execution_mode.rs000064400000000000000000000016271046102023000200560ustar 00000000000000#[derive(Debug, Clone, Copy, PartialEq)] pub enum ExternalExecutionMode { /// executed in the parent shell, on broot leaving, using the `br` function FromParentShell, /// executed on broot leaving, not necessarily in the parent shell LeaveBroot, /// executed in a sub process without quitting broot StayInBroot, } impl ExternalExecutionMode { pub fn is_from_shell(self) -> bool { matches!(self, Self::FromParentShell) } pub fn is_leave_broot(self) -> bool { !matches!(self, Self::StayInBroot) } pub fn from_conf( from_shell: Option, // default is false leave_broot: Option, // default is true ) -> Self { if from_shell.unwrap_or(false) { Self::FromParentShell } else if leave_broot.unwrap_or(true) { Self::LeaveBroot } else { Self::StayInBroot } } } broot-1.46.3/src/verb/file_type_condition.rs000064400000000000000000000036701046102023000171730ustar 00000000000000use { crate::{ app::SelectionType, content_type, tree::{TreeLine, TreeLineType}, }, serde::{Deserialize, Serialize}, std::path::Path, }; #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize, Serialize)] #[serde(rename_all = "snake_case")] pub enum FileTypeCondition { #[default] Any, // directory or link to a directory Directory, File, TextFile, BinaryFile, } impl FileTypeCondition { pub fn is_default(&self) -> bool { self == &Self::default() } pub fn accepts_path(self, path: &Path) -> bool { match self { Self::Any => true, Self::Directory => path.is_dir(), Self::File => path.is_file(), Self::TextFile => { path.is_file() && matches!(content_type::is_file_text(path), Ok(true)) } Self::BinaryFile => { path.is_file() && matches!(content_type::is_file_binary(path), Ok(true)) } } } pub fn accepts_line(self, line: &TreeLine) -> bool { match self { Self::Any => true, Self::Directory => line.is_dir(), Self::File => matches!(line.line_type, TreeLineType::File), Self::TextFile => { line.is_file() && matches!(content_type::is_file_text(&line.path), Ok(true)) } Self::BinaryFile => { line.is_file() && matches!(content_type::is_file_binary(&line.path), Ok(true)) } } } /// a little clunky, should be used only on well defined cases, like documenting /// internals pub fn accepts_selection_type( self, stype: SelectionType, ) -> bool { match (self, stype) { (Self::Any, _) => true, (Self::Directory, SelectionType::Directory) => true, (Self::File, SelectionType::File) => true, _ => false, } } } broot-1.46.3/src/verb/internal.rs000064400000000000000000000251701046102023000147600ustar 00000000000000//! declare the internal functions which may be used in verbs. //! They don't take any user argument other than the selection //! (this may change if the needs arise). //! They can be called as ":some_name" from builtin verbs and //! from configured verbs. use { crate::errors::ConfError, }; macro_rules! Internals { ( $($name:ident: $description:literal $need_path:literal,)* ) => { #[derive(Debug, Clone, Copy, PartialEq)] #[allow(non_camel_case_types)] pub enum Internal { $($name,)* } impl Internal { pub fn try_from(verb: &str) -> Result { use Internal::*; match verb { $(stringify!($name) => Ok($name),)* _ => Err(ConfError::UnknownInternal{ verb: verb.to_string() }), } } } impl Internal { pub fn name(self) -> &'static str { use Internal::*; match self { $($name => stringify!($name),)* } } pub fn description(self) -> &'static str { use Internal::*; match self { $($name => $description,)* } } pub fn need_path(self) -> bool { use Internal::*; match self { $($name => $need_path,)* } } } } } // internals: // name: "description" needs_a_path Internals! { apply_flags: "apply flags (eg `-sd` to show sizes and dates)" false, back: "revert to the previous state (mapped to *esc*)" false, default_layout: "restore default panel sizes" false, clear_output: "clear the --verb-output file" false, clear_stage: "empty the staging area" false, close_panel_cancel: "close the panel, not using the selected path" false, close_panel_ok: "close the panel, validating the selected path" false, close_preview: "close the preview panel" false, close_staging_area: "close the staging area panel" false, copy_line: "copy selected line (in tree or preview)" true, copy_path: "copy path to system clipboard" true, escape: "escape from edition, completion, page, etc." false, filesystems: "list mounted filesystems" false, focus: "display the directory (mapped to *enter*)" true, focus_staging_area_no_open: "focus the staging area if already open" false, help: "display broot's help" false, input_clear: "empty the input" false, input_del_char_below: "delete the char left at the cursor's position" false, input_del_char_left: "delete the char left of the cursor" false, input_del_word_left: "delete the word left of the cursor" false, input_del_word_right: "delete the word right of the cursor" false, input_go_left: "move the cursor to the left" false, input_go_right: "move the cursor to the right" false, input_go_to_end: "move the cursor to the end of input" false, input_go_to_start: "move the cursor to the start of input" false, input_go_word_left: "move the cursor one word to the left" false, input_go_word_right: "move the cursor one word to the right" false, input_paste: "paste the clipboard content into the input" false, input_selection_copy: "copy the selected part of the input into the selection" false, input_selection_cut: "cut the selected part of the input into the selection" false, line_down: "move one line down" false, line_down_no_cycle: "move one line down" false, line_up: "move one line up" false, line_up_no_cycle: "move one line up" false, mode_command: "enter the command mode" false, mode_input: "enter the input mode" false, move_panel_divider: "move a panel divider" false, next_dir: "select the next directory" false, next_match: "select the next match" false, next_same_depth: "select the next file at the same depth" false, no_sort: "don't sort" false, open_leave: "open file or directory according to OS (quit broot)" true, open_preview: "open the preview panel" true, open_staging_area: "open the staging area" false, open_stay: "open file or directory according to OS (stay in broot)" true, open_stay_filter: "display the directory, keeping the current pattern" true, open_trash: "show the content of the trash" false, page_down: "scroll one page down" false, page_up: "scroll one page up" false, panel_left: "focus or open panel on left" false, panel_left_no_open: "focus panel on left" false, panel_right: "focus or open panel on right" false, panel_right_no_open: "focus panel on right" false, parent: "move to the parent directory" false, preview_binary: "preview the selection as binary" true, preview_image: "preview the selection as image" true, preview_text: "preview the selection as text" true, previous_dir: "select the previous directory" false, previous_match: "select the previous match" false, previous_same_depth: "select the previous file at the same depth" false, print_path: "print path and leaves broot" true, print_relative_path: "print relative path and leaves broot" true, print_tree: "print tree and leaves broot" true, quit: "quit Broot" false, refresh: "refresh tree and clear size cache" false, delete_trashed_file: "irreversibly delete a file which is in the trash" false, restore_trashed_file: "restore a file which is in the trash" false, purge_trash: "irreversibly delete the trash's content" false, root_down: "move tree root down" true, root_up: "move tree root up" true, select: "select a file by path" true, show: "reveal and select a file by path" true, select_first: "select the first item" false, select_last: "select the last item" false, set_panel_width: "set the width of a panel" false, set_syntax_theme: "set the theme of code preview" false, sort_by_count: "sort by count" false, sort_by_date: "sort by date" false, sort_by_size: "sort by size" false, sort_by_type: "sort by type" false, sort_by_type_dirs_first: "sort by type, dirs first" false, sort_by_type_dirs_last: "sort by type, dirs last" false, stage: "add selection to staging area" true, stage_all_directories: "stage all matching directories" true, stage_all_files: "stage all matching files" true, start_end_panel: "either open or close an additional panel" true, toggle_counts: "toggle showing number of files in directories" false, toggle_dates: "toggle showing last modified dates" false, toggle_device_id: "toggle showing device id" false, toggle_files: "toggle showing files (or just folders)" false, toggle_git_file_info: "toggle display of git file information" false, toggle_git_ignore: "toggle use of .gitignore and .ignore" false, toggle_git_status: "toggle showing only files relevant for git status" false, toggle_hidden: "toggle showing hidden files" false, toggle_ignore: "toggle use of .gitignore and .ignore" false, toggle_perm: "toggle showing file permissions" false, toggle_preview: "open/close the preview panel" false, toggle_root_fs: "toggle showing filesystem info on top" false, set_max_depth: "set the maximum directory depth shown" false, unset_max_depth: "clear the max_depth" false, toggle_second_tree: "toggle display of a second tree panel" true, toggle_sizes: "toggle showing sizes" false, toggle_stage: "add or remove selection to staging area" true, toggle_staging_area: "open/close the staging area panel" false, toggle_tree: "toggle showing more than one level of the tree" true, toggle_trim_root: "toggle removing nodes at first level too" false, total_search: "search again but on all children" false, search_again: "either put back last search, or search deeper" false, trash: "move file to system trash" true, unstage: "remove selection from staging area" true, up_tree: "focus the parent of the current root" true, write_output: "write the argument to the --verb-output file" false, //restore_pattern: "restore a pattern which was just removed" false, } impl Internal { pub fn invocation_pattern(self) -> &'static str { match self { Self::apply_flags => r"-(?P\w+)?", Self::focus => r"focus (?P.*)?", Self::select => r"select (?P.*)?", Self::show => r"show (?P.*)?", Self::line_down => r"line_down (?P\d*)?", Self::line_up => r"line_up (?P\d*)?", Self::line_down_no_cycle => r"line_down_no_cycle (?P\d*)?", Self::line_up_no_cycle => r"line_up_no_cycle (?P\d*)?", Self::move_panel_divider => r"move_panel_divider (?P\d+) (?P-?\d+)", Self::set_panel_width => r"set_panel_width (?P\d+) (?P\d+)", Self::set_max_depth => r"set_max_depth (?P\d+)", Self::set_syntax_theme => r"set_syntax_theme {theme:theme}", Self::write_output => r"write_output (?P.*)", _ => self.name(), } } pub fn exec_pattern(self) -> &'static str { match self { Self::apply_flags => r"apply_flags {flags}", Self::focus => r"focus {path}", Self::line_down => r"line_down {count}", Self::line_up => r"line_up {count}", Self::line_down_no_cycle => r"line_down_no_cycle {count}", Self::line_up_no_cycle => r"line_up_no_cycle {count}", Self::move_panel_divider => r"move_panel_divider {idx} {dx}", Self::set_panel_width => r"set_panel_width {idx} {width}", Self::write_output => r"write_output {line}", _ => self.name(), } } pub fn needs_selection(self, arg: &Option) -> bool { match self { Internal::focus => arg.is_none(), _ => self.need_path(), } } pub fn is_input_related(self) -> bool { match self { Self::input_clear => true, Self::input_del_char_below => true, Self::input_del_char_left => true, Self::input_del_word_left => true, Self::input_del_word_right => true, Self::input_go_left => true, Self::input_go_right => true, Self::input_go_to_end => true, Self::input_go_to_start => true, Self::input_go_word_left => true, Self::input_go_word_right => true, Self::input_paste => true, Self::input_selection_copy => true, Self::input_selection_cut => true, _ => false, } } } broot-1.46.3/src/verb/internal_execution.rs000064400000000000000000000030251046102023000170360ustar 00000000000000use { super::*, crate::errors::ConfError, std::fmt, }; /// A verb execution definition based on an internal #[derive(Debug, Clone)] pub struct InternalExecution { /// the internal to use pub internal: Internal, /// whether to open the resulting state in a new panel /// instead of the current ones pub bang: bool, /// arguments /// (for example `"~"` when a verb execution is `:!focus ~`) pub arg: Option, } impl InternalExecution { pub fn from_internal(internal: Internal) -> Self { Self { internal, bang: false, arg: None, } } pub fn from_internal_bang(internal: Internal, bang: bool) -> Self { Self { internal, bang, arg: None, } } pub fn try_from(invocation_str: &str) -> Result { let invocation = VerbInvocation::from(invocation_str); let internal = Internal::try_from(&invocation.name)?; Ok(Self { internal, bang: invocation.bang, arg: invocation.args, }) } pub fn needs_selection(&self) -> bool { self.internal.needs_selection(&self.arg) } } impl fmt::Display for InternalExecution { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, ":{}", self.internal.name())?; if self.bang { write!(f, "!")?; } if let Some(arg) = &self.arg { write!(f, " {arg}")?; } Ok(()) } } broot-1.46.3/src/verb/internal_focus.rs000064400000000000000000000201011046102023000161440ustar 00000000000000//! utility functions to help handle the `:focus` internal use { super::*, crate::{ app::*, browser::BrowserState, command::TriggerType, display::Screen, path::{self, PathAnchor}, pattern::InputPattern, preview::PreviewState, task_sync::Dam, tree::TreeOptions, }, std::{ path::{Path, PathBuf}, }, }; pub fn on_path( path: PathBuf, screen: Screen, tree_options: TreeOptions, in_new_panel: bool, con: &AppContext, ) -> CmdResult { if in_new_panel { new_panel_on_path( path, screen, tree_options, PanelPurpose::None, con, HDir::Right, ) } else { new_state_on_path(path, screen, tree_options, con) } } pub fn new_state_on_path( path: PathBuf, screen: Screen, tree_options: TreeOptions, con: &AppContext, ) -> CmdResult { let path = path::closest_dir(&path); CmdResult::from_optional_browser_state( BrowserState::new(path, tree_options, screen, con, &Dam::unlimited()), None, false, ) } #[allow(unused_mut)] pub fn new_panel_on_path( mut path: PathBuf, screen: Screen, mut tree_options: TreeOptions, purpose: PanelPurpose, con: &AppContext, direction: HDir, ) -> CmdResult { #[cfg(not(windows))] // We try to canonicalize the path, mostly to resolve links // We don't do it on Windows due to issue #809 if let Ok(canonic) = std::fs::canonicalize(&path) { path = canonic; // If it can't be canonicalized, we'll let the panel state // deal with the original path } if purpose.is_preview() { let pattern = tree_options.pattern.tree_to_preview(); CmdResult::NewPanel { state: Box::new(PreviewState::new(path, pattern, None, tree_options, con)), purpose, direction, } } else { let path = path::closest_dir(&path); // We remove the pattern on opening another browser. This will probably // be configuratble with a clear_pattern verb option in the future tree_options.pattern = InputPattern::none(); match BrowserState::new(path, tree_options, screen, con, &Dam::unlimited()) { Ok(os) => CmdResult::NewPanel { state: Box::new(os), purpose, direction, }, Err(e) => CmdResult::DisplayError(e.to_string()), } } } /// Compute the path to go to in case of the internal being triggered from /// the input. /// /// This path depends on the verb (which may hardcore the path or have a /// pattern), from the selection, fn path_from_input( verb: &Verb, internal_exec: &InternalExecution, base_path: &Path, // either the selected path or the root path input_arg: Option<&String>, app_state: &AppState, con: &AppContext, ) -> PathBuf { match (input_arg, internal_exec.arg.as_ref()) { (Some(input_arg), Some(verb_arg)) => { // The verb probably defines some patttern which uses the input. // For example: // { // invocation: "gotar {path}" // execution: ":focus {path}/target" // } // (or that input is useless) let path_builder = ExecutionStringBuilder::with_invocation( verb.invocation_parser.as_ref(), SelInfo::from_path(base_path), app_state, Some(input_arg), ); path_builder.path(verb_arg, con) } (Some(input_arg), None) => { // the verb defines nothing // The :focus internal execution was triggered from the // input (which must be a kind of alias for :focus) // so we do exactly what the input asks for path::path_from(base_path, PathAnchor::Unspecified, input_arg) } (None, Some(verb_arg)) => { // the verb defines the path where to go.. // the internal_execution specifies the path to use // (it may come from a configured verb whose execution is // `:focus some/path`). // The given path may be relative hence the need for the // state's selection // (we assume a check before ensured it doesn't need an input) let path_builder = ExecutionStringBuilder::with_invocation( verb.invocation_parser.as_ref(), SelInfo::from_path(base_path), app_state, None, ); path_builder.path(verb_arg, con) } (None, None) => { // user only wants to open the selected path, either in the same panel or // in a new one base_path.to_path_buf() } } } pub fn get_status_markdown( verb: &Verb, internal_exec: &InternalExecution, sel_info: SelInfo<'_>, invocation: &VerbInvocation, app_state: &AppState, con: &AppContext, ) -> String { let base_path = sel_info .one_path() .unwrap_or(&app_state.root); let path = path_from_input( verb, internal_exec, base_path, invocation.args.as_ref(), app_state, con, ); format!("Hit *enter* to focus `{}`", path.to_string_lossy()) } /// general implementation for verbs based on the :focus internal with optionally /// a bang or an argument. pub fn on_internal( internal_exec: &InternalExecution, input_invocation: Option<&VerbInvocation>, trigger_type: TriggerType, selected_path: &Path, tree_options: TreeOptions, app_state: & AppState, cc: &CmdContext, ) -> CmdResult { let con = &cc.app.con; let screen = cc.app.screen; let bang = input_invocation .map(|inv| inv.bang) .unwrap_or(internal_exec.bang); let input_arg = input_invocation.as_ref() .and_then(|invocation| invocation.args.as_ref()); match trigger_type { TriggerType::Input(verb) => { let path = path_from_input( verb, internal_exec, selected_path, input_arg, app_state, cc.app.con, ); on_path(path, screen, tree_options, bang, con) } _ => { // the :focus internal was triggered by a key if let Some(arg) = &internal_exec.arg { // the internal_execution specifies the path to use // (it may come from a configured verb whose execution is // `:focus some/path` or `:focus {initial-root}̀). // The given path may be relative hence the need for the // state's selection let path_builder = ExecutionStringBuilder::without_invocation( SelInfo::from_path(selected_path), app_state, ); let path = path_builder.path(arg, con); let bang = input_invocation .map(|inv| inv.bang) .unwrap_or(internal_exec.bang); on_path(path, screen, tree_options, bang, con) } else if let Some(input_arg) = input_arg { // the :focus internal was triggered by a key, and without internal arg, // which means the user wants to explore the arg with purpose // of selecting a path let base_dir = selected_path.to_string_lossy(); let path = path::path_from(&*base_dir, PathAnchor::Unspecified, input_arg); let arg_type = SelectionType::Any; // We might do better later let purpose = PanelPurpose::ArgEdition { arg_type }; new_panel_on_path(path, screen, tree_options, purpose, con, HDir::Right) } else { // user only wants to open the selected path, either in the same panel or // in a new one on_path(selected_path.to_path_buf(), screen, tree_options, bang, con) } } } } broot-1.46.3/src/verb/internal_path.rs000064400000000000000000000107131046102023000157710ustar 00000000000000//! utility functions to help handle some internals which require a path use { super::*, crate::{ app::*, browser::BrowserState, command::TriggerType, display::Screen, path::{self, PathAnchor}, tree::Tree, }, std::path::{Path, PathBuf}, }; /// general implementation for verbs on the form ":some_internal path" pub fn determine_path( internal_exec: &InternalExecution, input_invocation: Option<&VerbInvocation>, trigger_type: TriggerType, tree: &Tree, app_state: & AppState, cc: &CmdContext, ) -> Option { info!( "internal_path::determine_path internal_exec={:?} input_invocation={:?} trygger_type={:?}", internal_exec, input_invocation, trigger_type, ); let input_arg = input_invocation.as_ref() .and_then(|invocation| invocation.args.as_ref()); match trigger_type { TriggerType::Input(verb) => { let path = path_from_input( verb, internal_exec, &tree.selected_line().path, input_arg, app_state, cc.app.con, ); Some(path) } _ => { // the :select internal was triggered by a key if let Some(arg) = &internal_exec.arg { // the internal_execution specifies the path to use // (it may come from a configured verb whose execution is // `:select some/path`). // The given path may be relative hence the need for the // state's selection let path = path::path_from( &tree.selected_line().path, PathAnchor::Unspecified, arg, ); Some(path) } else { // there's nothing really to do here None } } } } /// Compute the path to go to in case of the internal being triggered from /// the input. /// /// This path depends on the verb (which may hardcore the path or have a /// pattern), from the selection, fn path_from_input( verb: &Verb, internal_exec: &InternalExecution, base_path: &Path, // either the selected path or the root path input_arg: Option<&String>, app_state: &AppState, con: &AppContext, ) -> PathBuf { match (input_arg, internal_exec.arg.as_ref()) { (Some(input_arg), Some(verb_arg)) => { // The verb probably defines some pattern which uses the input. // For example: // { // invocation: "gotar {path}" // execution: ":select {path}/target" // } // (or that input is useless) let path_builder = ExecutionStringBuilder::with_invocation( verb.invocation_parser.as_ref(), SelInfo::from_path(base_path), app_state, Some(input_arg), ); path_builder.path(verb_arg, con) } (Some(input_arg), None) => { // the verb defines nothing // The :select internal execution was triggered from the // input (which must be a kind of alias for :select) // so we do exactly what the input asks for path::path_from(base_path, PathAnchor::Unspecified, input_arg) } (None, Some(verb_arg)) => { // the verb defines the path where to go.. // the internal_execution specifies the path to use // (it may come from a configured verb whose execution is // `:select some/path`). // The given path may be relative hence the need for the // state's selection // (we assume a check before ensured it doesn't need an input) path::path_from(base_path, PathAnchor::Unspecified, verb_arg) } (None, None) => { // This doesn't really make sense: we're selecting the currently // selected path base_path.to_path_buf() } } } pub fn on_path( path: PathBuf, tree: &mut Tree, screen: Screen, in_new_panel: bool, ) -> CmdResult { debug!("executing :select on path {:?}", &path); if in_new_panel { warn!("bang in :select isn't supported yet"); } if tree.try_select_path(&path) { tree.make_selection_visible(BrowserState::page_height(screen)); } CmdResult::Keep } broot-1.46.3/src/verb/internal_select.rs000064400000000000000000000025401046102023000163130ustar 00000000000000//! utility functions to help handle the `:select` internal use { super::*, crate::{ app::*, browser::BrowserState, command::TriggerType, display::Screen, tree::Tree, }, std::path::PathBuf, }; /// general implementation for verbs based on the :select internal with optionally /// a bang or an argument. pub fn on_internal( internal_exec: &InternalExecution, input_invocation: Option<&VerbInvocation>, trigger_type: TriggerType, tree: &mut Tree, app_state: & AppState, cc: &CmdContext, ) -> CmdResult { let Some(path) = internal_path::determine_path( internal_exec, input_invocation, trigger_type, tree, app_state, cc, ) else { return CmdResult::Keep; }; let screen = cc.app.screen; let bang = input_invocation .map(|inv| inv.bang) .unwrap_or(internal_exec.bang); on_path(path, tree, screen, bang) } pub fn on_path( path: PathBuf, tree: &mut Tree, screen: Screen, in_new_panel: bool, ) -> CmdResult { debug!("executing :select on path {:?}", &path); if in_new_panel { warn!("bang in :select isn't supported yet"); } if tree.try_select_path(&path) { tree.make_selection_visible(BrowserState::page_height(screen)); } CmdResult::Keep } broot-1.46.3/src/verb/invocation_parser.rs000064400000000000000000000113441046102023000166670ustar 00000000000000use { super::*, crate::{ app::*, errors::ConfError, path::PathAnchor, }, regex::Regex, rustc_hash::FxHashMap, std::{ path::PathBuf, }, }; /// Definition of how the user input should be checked /// and maybe parsed to provide the arguments used /// for execution or description. #[derive(Debug)] pub struct InvocationParser { /// pattern of how the command is supposed to be typed in the input pub invocation_pattern: VerbInvocation, /// a regex to read the arguments in the user input /// This regex declares named groups, with the name being the /// name of the replacement variable (this implies that an /// invocation name's characters are [_0-9a-zA-Z.\[\]]) args_parser: Option, pub arg_defs: Vec, } impl InvocationParser { pub fn new( invocation_str: &str, ) -> Result { let invocation_pattern = VerbInvocation::from(invocation_str); let mut args_parser = None; let mut arg_defs = Vec::new(); if let Some(args) = &invocation_pattern.args { let spec = GROUP.replace_all(args, r"(?P<$1>.+)"); let spec = format!("^{spec}$"); args_parser = match Regex::new(&spec) { Ok(regex) => Some(regex), Err(_) => { return Err(ConfError::InvalidVerbInvocation { invocation: spec }); } }; for group in GROUP.find_iter(args) { let group_str = group.as_str(); arg_defs.push( if group_str.ends_with("path-from-parent}") { ArgDef::Path { anchor: PathAnchor::Parent, selection_type: SelectionType::Any, } } else if group_str.ends_with("path-from-directory}") { ArgDef::Path { anchor: PathAnchor::Directory, selection_type: SelectionType::Any, } } else if group_str.ends_with("path}") { ArgDef::Path { anchor: PathAnchor::Unspecified, selection_type: SelectionType::Any, } } else if group_str.ends_with("theme}") { ArgDef::Theme } else { ArgDef::Unspecified // still probably a path } ); } } Ok(Self { invocation_pattern, args_parser, arg_defs, }) } pub fn name(&self) -> &str { &self.invocation_pattern.name } pub fn get_unique_arg_def(&self) -> Option { (self.arg_defs.len() == 1) .then(|| self.arg_defs[0]) } pub fn get_unique_arg_anchor(&self) -> PathAnchor { if self.arg_defs.len() == 1 { if let ArgDef::Path { anchor, .. } = self.arg_defs[0] { return anchor; } } PathAnchor::Unspecified } /// Assuming the verb has been matched, check whether the arguments /// are OK according to the regex. Return none when there's no problem /// and return the error to display if arguments don't match pub fn check_args( &self, invocation: &VerbInvocation, _other_path: &Option, ) -> Option { match (&invocation.args, &self.args_parser) { (None, None) => None, (None, Some(ref regex)) => { if regex.is_match("") { None } else { Some(self.invocation_pattern.to_string_for_name(&invocation.name)) } } (Some(ref s), Some(ref regex)) => { if regex.is_match(s) { None } else { Some(self.invocation_pattern.to_string_for_name(&invocation.name)) } } (Some(_), None) => Some(format!("{} doesn't take arguments", invocation.name)), } } pub fn parse(&self, args: &str) -> Option> { self.args_parser.as_ref() .map(|r| { let mut map = FxHashMap::default(); if let Some(input_cap) = r.captures(args) { for name in r.capture_names().flatten() { if let Some(c) = input_cap.name(name) { map.insert(name.to_string(), c.as_str().to_string()); } } } map }) } } broot-1.46.3/src/verb/mod.rs000064400000000000000000000030551046102023000137210ustar 00000000000000mod arg_def; mod exec_pattern; mod execution_builder; mod external_execution; mod external_execution_mode; mod file_type_condition; mod internal; mod internal_execution; pub mod internal_focus; pub mod internal_select; pub mod internal_path; mod invocation_parser; mod sequence_execution; mod verb; mod verb_description; mod verb_execution; mod verb_invocation; mod verb_store; mod write; pub use { arg_def::*, exec_pattern::*, execution_builder::ExecutionStringBuilder, external_execution::ExternalExecution, external_execution_mode::ExternalExecutionMode, file_type_condition::*, internal::Internal, internal_execution::InternalExecution, invocation_parser::InvocationParser, once_cell::sync::Lazy, sequence_execution::SequenceExecution, verb::Verb, verb_description::VerbDescription, verb_execution::VerbExecution, verb_invocation::*, verb_store::{PrefixSearchResult, VerbStore}, write::*, }; use { lazy_regex::*, }; /// the group you find in invocation patterns and execution patterns pub static GROUP: Lazy = lazy_regex!(r"\{([^{}:]+)(?::([^{}:]+))?\}"); pub type VerbId = usize; pub fn str_has_selection_group(s: &str) -> bool { GROUP.find_iter(s) .any(|group| matches!( group.as_str(), "{file}" | "{file-name}" | "{parent}" | "{directory}", )) } pub fn str_has_other_panel_group(s: &str) -> bool { for group in GROUP.find_iter(s) { if group.as_str().starts_with("{other-panel-") { return true; } } false } broot-1.46.3/src/verb/sequence_execution.rs000064400000000000000000000003251046102023000170320ustar 00000000000000 use { crate::{ command::Sequence, }, }; /// A verb execution definition based on a sequence /// of commands #[derive(Debug, Clone)] pub struct SequenceExecution { pub sequence: Sequence, } broot-1.46.3/src/verb/verb.rs000064400000000000000000000246531046102023000141070ustar 00000000000000use { super::*, crate::{ app::*, errors::ConfError, path::PathAnchor, }, crokey::{ KeyCombination, }, std::{ cmp::PartialEq, path::PathBuf, ptr, }, }; /// what makes a verb. /// /// Verbs are the engines of broot commands, and apply /// - to the selected file (if user-defined, then must contain {file}, {parent} or {directory}) /// - to the current app state /// /// There are two types of verbs executions: /// - external programs or commands (cd, mkdir, user defined commands, etc.) /// - internal behaviors (focusing a path, going back, showing the help, etc.) /// /// Some verbs are builtins, some other ones are created by configuration. /// /// Both builtins and configured vers can be internal or external based. /// /// Verbs can't be cloned. Two verbs are equal if they have the same address /// in memory. #[derive(Debug)] pub struct Verb { pub id: VerbId, /// names (like "cd", "focus", "focus_tab", "c") by which /// a verb can be called. /// Can be empty if the verb is only called with a key shortcut. /// Right now there's no way for it to contain more than 2 elements /// but this may change. pub names: Vec, /// key shortcuts pub keys: Vec, /// how the input must be checked and interpreted /// Can be empty if the verb is only called with a key shortcut. pub invocation_parser: Option, /// how the verb will be executed pub execution: VerbExecution, /// a description pub description: VerbDescription, /// the type of selection this verb applies to pub selection_condition: FileTypeCondition, /// extension filtering. If empty, all extensions apply pub file_extensions: Vec, /// whether the verb needs a selection pub needs_selection: bool, /// whether we need to have a secondary panel for execution /// (which is the case when the execution pattern has {other-panel-file}) pub needs_another_panel: bool, /// if true (default) verbs are directly executed when /// triggered with a keyboard shortcut pub auto_exec: bool, /// whether to show the verb in help screen /// (if we show all input related actions, the doc is unusable) pub show_in_doc: bool, pub panels: Vec, } impl PartialEq for Verb { fn eq(&self, other: &Self) -> bool { ptr::eq(self, other) } } impl Verb { pub fn new( id: VerbId, invocation_str: Option<&str>, execution: VerbExecution, description: VerbDescription, ) -> Result { let invocation_parser = invocation_str.map(InvocationParser::new).transpose()?; let mut names = Vec::new(); if let Some(ref invocation_parser) = invocation_parser { let name = invocation_parser.name().to_string(); check_verb_name(&name)?; names.push(name); } let ( needs_selection, needs_another_panel, ) = match &execution { VerbExecution::Internal(ie) => ( ie.needs_selection(), false, ), VerbExecution::External(ee) => ( ee.exec_pattern.has_selection_group(), ee.exec_pattern.has_other_panel_group(), ), VerbExecution::Sequence(se) => ( se.sequence.has_selection_group(), se.sequence.has_other_panel_group(), ) }; Ok(Self { id, names, keys: Vec::new(), invocation_parser, execution, description, selection_condition: FileTypeCondition::Any, file_extensions: Vec::new(), needs_selection, needs_another_panel, auto_exec: true, show_in_doc: true, panels: Vec::new(), }) } pub fn with_key(&mut self, key: KeyCombination) -> &mut Self { self.keys.push(key); self } pub fn add_keys(&mut self, keys: Vec) { for key in keys { self.keys.push(key); } } pub fn no_doc(&mut self) -> &mut Self { self.show_in_doc = false; self } pub fn with_name(&mut self, name: &str) -> Result<&mut Self, ConfError> { check_verb_name(name)?; self.names.insert(0, name.to_string()); Ok(self) } pub fn with_description(&mut self, description: &str) -> &mut Self { self.description = VerbDescription::from_text(description.to_string()); self } pub fn with_shortcut(&mut self, shortcut: &str) -> &mut Self { self.names.push(shortcut.to_string()); self } pub fn with_condition(&mut self, selection_condition: FileTypeCondition) -> &mut Self { self.selection_condition = selection_condition; self } pub fn needing_another_panel(&mut self) -> &mut Self { self.needs_another_panel = true; self } pub fn with_auto_exec(&mut self, b: bool) -> &mut Self { self.auto_exec = b; self } pub fn has_name(&self, searched_name: &str) -> bool { self.names.iter().any(|name| name == searched_name) } /// Assuming the verb has been matched, check whether the arguments /// are OK according to the regex. Return none when there's no problem /// and return the error to display if arguments don't match. pub fn check_args( &self, sel_info: SelInfo<'_>, invocation: &VerbInvocation, other_path: &Option, ) -> Option { match sel_info { SelInfo::None => self.check_sel_args(None, invocation, other_path), SelInfo::One(sel) => self.check_sel_args(Some(sel), invocation, other_path), SelInfo::More(stage) => { stage.paths().iter() .filter_map(|path| { let sel = Selection { path, line: 0, stype: SelectionType::from(path), is_exe: false, }; self.check_sel_args(Some(sel), invocation, other_path) }) .next() } } } fn check_sel_args( &self, sel: Option>, invocation: &VerbInvocation, other_path: &Option, ) -> Option { if self.needs_selection && sel.is_none() { Some("This verb needs a selection".to_string()) } else if self.needs_another_panel && other_path.is_none() { Some("This verb needs exactly two panels".to_string()) } else if let Some(ref parser) = self.invocation_parser { parser.check_args(invocation, other_path) } else if invocation.args.is_some() { Some("This verb doesn't take arguments".to_string()) } else { None } } pub fn get_status_markdown( &self, sel_info: SelInfo<'_>, app_state: &AppState, invocation: &VerbInvocation, con: &AppContext, ) -> String { let name = self.names.first().unwrap_or(&invocation.name); // there's one special case: the ̀ :focus` internal. As long // as no other internal takes args, and no other verb can // have an optional argument, I don't try to build a // generic behavior for internal optionally taking args and // thus I hardcode the test here. if let VerbExecution::Internal(internal_exec) = &self.execution { if internal_exec.internal == Internal::focus { return internal_focus::get_status_markdown( self, internal_exec, sel_info, invocation, app_state, con, ); } } let builder = || { ExecutionStringBuilder::with_invocation( self.invocation_parser.as_ref(), sel_info, app_state, invocation.args.as_ref(), ) }; if let VerbExecution::Sequence(seq_ex) = &self.execution { // We can't determine before execution what will be the arguments, except // for the first item of the sequence. It's cleaner to just not try expand it format!("Hit *enter* to **{}**: `{}`", name, seq_ex.sequence.raw) } else if let VerbExecution::External(external_exec) = &self.execution { let exec_desc = builder().shell_exec_string(&external_exec.exec_pattern, con); format!("Hit *enter* to **{}**: `{}`", name, &exec_desc) } else if self.description.code { format!("Hit *enter* to **{}**: `{}`", name, &self.description.content) } else { format!("Hit *enter* to **{}**: {}", name, &self.description.content) } } pub fn get_unique_arg_anchor(&self) -> PathAnchor { self.invocation_parser .as_ref() .map_or(PathAnchor::Unspecified, InvocationParser::get_unique_arg_anchor) } pub fn get_internal(&self) -> Option { match &self.execution { VerbExecution::Internal(internal_exec) => Some(internal_exec.internal), _ => None, } } pub fn is_internal(&self, internal: Internal) -> bool { self.get_internal() == Some(internal) } pub fn is_some_internal(v: Option<&Verb>, internal: Internal) -> bool { v.map_or(false, |v| v.is_internal(internal)) } pub fn is_sequence(&self) -> bool { matches!(self.execution, VerbExecution::Sequence(_)) } pub fn can_be_called_in_panel(&self, panel_state_type: PanelStateType) -> bool { self.panels.is_empty() || self.panels.contains(&panel_state_type) } pub fn accepts_extension(&self, extension: Option<&str>) -> bool { if self.file_extensions.is_empty() { true } else { extension .map_or(false, |ext| self.file_extensions.iter().any(|ve| ve == ext)) } } } pub fn check_verb_name(name: &str) -> Result<(), ConfError> { if regex_is_match!(r"^([@,#~&'%$\dù_-]+|[\w][\w_@,#~&'%$\dù_-]*)+$", name) { Ok(()) } else { Err(ConfError::InvalidVerbName{ name: name.to_string() }) } } broot-1.46.3/src/verb/verb_description.rs000064400000000000000000000006461046102023000165060ustar 00000000000000/// how a verb is described in the help screen #[derive(Debug, Clone)] pub struct VerbDescription { pub code: bool, pub content: String, } impl VerbDescription { pub fn from_code(content: String) -> Self { Self { code: true, content, } } pub fn from_text(content: String) -> Self { Self { code: false, content, } } } broot-1.46.3/src/verb/verb_execution.rs000064400000000000000000000015431046102023000161630ustar 00000000000000use { super::*, std::fmt, }; /// how a verb must be executed #[derive(Debug, Clone)] pub enum VerbExecution { /// the verb execution is based on a behavior defined in code in Broot. /// Executions in conf starting with ":" are of this type. Internal(InternalExecution), /// the verb execution refers to a command that will be executed by the system, /// outside of broot. External(ExternalExecution), /// the execution is a sequence similar to what can be given /// to broot with --cmd Sequence(SequenceExecution), } impl fmt::Display for VerbExecution { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Internal(ie) => ie.fmt(f), Self::External(ee) => ee.exec_pattern.fmt(f), Self::Sequence(se) => se.sequence.raw.fmt(f), } } } broot-1.46.3/src/verb/verb_invocation.rs000064400000000000000000000212101046102023000163220ustar 00000000000000use { std::fmt, }; /// the verb and its arguments, making the invocation. /// When coming from parsing, the args is Some as soon /// as there's a separator (i.e. it's "" in "cp ") #[derive(Clone, Debug, PartialEq)] pub struct VerbInvocation { pub name: String, pub args: Option, pub bang: bool, } impl fmt::Display for VerbInvocation { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, ":")?; if self.bang { write!(f, "!")?; } write!(f, "{}", &self.name)?; if let Some(args) = &self.args { write!(f, " {}", &args)?; } Ok(()) } } impl VerbInvocation { pub fn new>(name: T, args: Option, bang: bool) -> Self { Self { name: name.into(), args: args.map(|s| s.into()), bang, } } pub fn is_empty(&self) -> bool { self.name.is_empty() } /// build a new String pub fn complete_name(&self) -> String { if self.bang { format!("{}_tab", &self.name) } else { self.name.clone() } } /// basically return the invocation but allow another name (the shortcut /// or a variant) pub fn to_string_for_name(&self, name: &str) -> String { let mut s = String::new(); if self.bang { s.push('!'); } s.push_str(name); if let Some(args) = &self.args { s.push(' '); s.push_str(args); } s } } impl From<&str> for VerbInvocation { /// Parse a string being or describing the invocation of a verb with its /// arguments and optional bang. The leading space or colon must /// have been stripped before. /// /// Examples: /// "mv" -> name: "mv" /// "!mv" -> name: "mv", bang /// "mv a b" -> name: "mv", args: "a b" /// "mv!a b" -> name: "mv", args: "a b", bang /// "a-b c" -> name: "a-b", args: "c", bang /// "-sp" -> name: "-", args: "sp" /// "-a b" -> name: "-", args: "a b" /// "-a b" -> name: "-", args: "a b" /// "--a" -> name: "--", args: "a" /// /// Notes: /// 1. A name is either "special" (only made of non alpha characters) /// or normal (starting with an alpha character). Special names don't /// need a space afterwards, as the first alpha character will start /// the args. /// 2. The space or colon after the name is optional if there's a bang /// after the name: the bang is the separator. /// 3. Duplicate separators before args are ignored (they're usually typos) /// 4. An opening parenthesis starts args fn from(invocation: &str) -> Self { let mut bang_before = false; let mut name = String::new(); let mut bang_after = false; let mut args: Option = None; let mut name_is_special = false; for c in invocation.chars() { if let Some(args) = args.as_mut() { if args.is_empty() && (c == ' ' || c == ':') { // we don't want args starting with a space just because // they're doubled or are optional after a special name } else { args.push(c); } continue; } if c == ' ' || c == ':' { args = Some(String::new()); continue; } if c == '(' { args = Some(c.to_string()); continue; } if c == '!' { if !name.is_empty() { bang_after = true; args = Some(String::new()); } else { bang_before = true; } continue; } if name.is_empty() { name.push(c); if !c.is_alphabetic() { name_is_special = true; } continue; } if c.is_alphabetic() && name_is_special { // this isn't part of the name anymore, it's part of the args args = Some(c.to_string()); continue; } name.push(c); } let bang = bang_before || bang_after; VerbInvocation { name, args, bang } } } #[cfg(test)] mod verb_invocation_tests { use super::*; #[test] fn check_special_chars() { assert_eq!( VerbInvocation::from("-sdp"), VerbInvocation::new("-", Some("sdp"), false), ); assert_eq!( VerbInvocation::from("!-sdp"), VerbInvocation::new("-", Some("sdp"), true), ); assert_eq!( VerbInvocation::from("-!sdp"), VerbInvocation::new("-", Some("sdp"), true), ); assert_eq!( VerbInvocation::from("-! sdp"), VerbInvocation::new("-", Some("sdp"), true), ); assert_eq!( VerbInvocation::from("!@a b"), VerbInvocation::new("@", Some("a b"), true), ); assert_eq!( VerbInvocation::from("!@%a b"), VerbInvocation::new("@%", Some("a b"), true), ); assert_eq!( VerbInvocation::from("22a b"), VerbInvocation::new("22", Some("a b"), false), ); assert_eq!( VerbInvocation::from("22!a b"), VerbInvocation::new("22", Some("a b"), true), ); assert_eq!( VerbInvocation::from("22 !a b"), VerbInvocation::new("22", Some("!a b"), false), ); assert_eq!( VerbInvocation::from("a$b4!r"), VerbInvocation::new("a$b4", Some("r"), true), ); assert_eq!( VerbInvocation::from("a-b c"), VerbInvocation::new("a-b", Some("c"), false), ); } #[test] fn check_verb_invocation_parsing_empty_arg() { // those tests focus mainly on the distinction between // None and Some("") for the args, distinction which matters // for inline help assert_eq!( VerbInvocation::from("!mv"), VerbInvocation::new("mv", None, true), ); assert_eq!( VerbInvocation::from("mva!"), VerbInvocation::new("mva", Some(""), true), ); assert_eq!( VerbInvocation::from("cp "), VerbInvocation::new("cp", Some(""), false), ); assert_eq!( VerbInvocation::from("cp ../"), VerbInvocation::new("cp", Some("../"), false), ); } #[test] fn check_verb_invocation_parsing_post_bang() { // ignoring post_bang (see issue #326) assert_eq!( VerbInvocation::from("mva!a"), VerbInvocation::new("mva", Some("a"), true), ); assert_eq!( VerbInvocation::from("!!!"), VerbInvocation::new("", None, true), ); } #[test] fn check_verb_invocation_parsing_empty_verb() { // there's currently no meaning for the empty verb, it's "reserved" // and will probably not be used as it may need a distinction between // one and two initial spaces in the input assert_eq!( VerbInvocation::from(""), VerbInvocation::new("", None, false), ); assert_eq!( VerbInvocation::from("!"), VerbInvocation::new("", None, true), ); assert_eq!( VerbInvocation::from("!! "), VerbInvocation::new("", Some(""), true), ); assert_eq!( VerbInvocation::from("!! a"), VerbInvocation::new("", Some("a"), true), ); } #[test] fn check_verb_invocation_parsing_oddities() { // checking some corner cases assert_eq!( VerbInvocation::from("!!a"), // the second bang is ignored VerbInvocation::new("a", None, true), ); assert_eq!( VerbInvocation::from("!!"), // the second bang is ignored VerbInvocation::new("", None, true), ); assert_eq!( VerbInvocation::from("a ! !"), VerbInvocation::new("a", Some("! !"), false), ); assert_eq!( VerbInvocation::from("!a !a"), VerbInvocation::new("a", Some("!a"), true), ); assert_eq!( VerbInvocation::from("a! ! //"), VerbInvocation::new("a", Some("! //"), true), ); assert_eq!( VerbInvocation::from(".. .."), VerbInvocation::new("..", Some(".."), false), ); } } broot-1.46.3/src/verb/verb_store.rs000064400000000000000000000602451046102023000153200ustar 00000000000000use { super::{ Internal, Verb, VerbId, }, crate::{ app::*, command::Sequence, conf::{Conf, VerbConf}, errors::ConfError, keys::KEY_FORMAT, keys, verb::*, }, crokey::*, }; /// Provide access to the verbs: /// - the built-in ones /// - the user defined ones /// /// A user defined verb can replace a built-in. /// /// When the user types some keys, we select a verb /// - if the input exactly matches a shortcut or the name /// - if only one verb name starts with the input pub struct VerbStore { verbs: Vec, unbound_keys: Vec, } #[derive(Debug, Clone, PartialEq)] pub enum PrefixSearchResult<'v, T> { NoMatch, Match(&'v str, T), Matches(Vec<&'v str>), } impl VerbStore { pub fn new(conf: &mut Conf) -> Result { let mut store = Self { verbs: Vec::new(), unbound_keys: Vec::new() }; for vc in &conf.verbs { if let Err(e) = store.add_from_conf(vc) { eprintln!("Invalid verb configuration: {}", e); warn!("Faulty parsed configuration: {:#?}", vc); if let Ok(toml) = toml::to_string(&vc) { eprintln!("Faulty configuration:\n{}", toml); } eprintln!("Configuration files:"); for path in &conf.files { eprintln!(" - {}", path.display()); } } } store.add_builtin_verbs()?; // at the end so that we can override them for key in store.unbound_keys.clone() { store.unbind_key(key)?; } Ok(store) } fn add_builtin_verbs( &mut self, ) -> Result<(), ConfError> { use super::{ExternalExecutionMode::*, Internal::*}; self.add_internal(escape).with_key(key!(esc)); // input actions, not visible in doc, but available for // example in remote control self.add_internal(input_clear).no_doc(); self.add_internal(input_del_char_left).no_doc(); self.add_internal(input_del_char_below).no_doc(); self.add_internal(input_del_word_left).no_doc(); self.add_internal(input_del_word_right).no_doc(); self.add_internal(input_go_to_end).with_key(key!(end)).no_doc(); self.add_internal(input_go_left).no_doc(); self.add_internal(input_go_right).no_doc(); self.add_internal(input_go_to_start).with_key(key!(home)).no_doc(); self.add_internal(input_go_word_left).no_doc(); self.add_internal(input_go_word_right).no_doc(); // arrow keys bindings self.add_internal(back); self.add_internal(open_stay); self.add_internal(line_down).with_key(key!(down)).with_key(key!('j')); self.add_internal(line_up).with_key(key!(up)).with_key(key!('k')); // changing display self.add_internal(set_syntax_theme); self.add_internal(apply_flags).with_name("apply_flags")?; self.add_internal(set_panel_width); self.add_internal(default_layout); // those two operations are mapped on ALT-ENTER, one // for directories and the other one for the other files self.add_internal(open_leave) // calls the system open .with_condition(FileTypeCondition::File) .with_key(key!(alt-enter)) .with_shortcut("ol"); self.add_external("cd", "cd {directory}", FromParentShell) .with_condition(FileTypeCondition::Directory) .with_key(key!(alt-enter)) .with_shortcut("ol") .with_description("change directory and quit"); #[cfg(unix)] self.add_external("chmod {args}", "chmod {args} {file}", StayInBroot) .with_condition(FileTypeCondition::File); #[cfg(unix)] self.add_external("chmod {args}", "chmod -R {args} {file}", StayInBroot) .with_condition(FileTypeCondition::Directory); self.add_internal(open_preview); self.add_internal(close_preview); self.add_internal(toggle_preview); self.add_internal(preview_image) .with_shortcut("img"); self.add_internal(preview_text) .with_shortcut("txt"); self.add_internal(preview_binary) .with_shortcut("hex"); self.add_internal(close_panel_ok); self.add_internal(close_panel_cancel) .with_key(key!(ctrl-w)); #[cfg(unix)] self.add_external( "copy {newpath}", "cp -r {file} {newpath:path-from-parent}", StayInBroot, ) .with_shortcut("cp"); #[cfg(windows)] self.add_external( "copy {newpath}", "xcopy /Q /H /Y /I {file} {newpath:path-from-parent}", StayInBroot, ) .with_shortcut("cp"); #[cfg(feature = "clipboard")] self.add_internal(copy_line) .with_key(key!(alt-c)); #[cfg(feature = "clipboard")] self.add_internal(copy_path); #[cfg(unix)] self.add_external( "copy_to_panel", "cp -r {file} {other-panel-directory}", StayInBroot, ) .with_shortcut("cpp"); #[cfg(windows)] self.add_external( "copy_to_panel", "xcopy /Q /H /Y /I {file} {other-panel-directory}", StayInBroot, ) .with_shortcut("cpp"); #[cfg(feature = "trash")] self.add_internal(trash); #[cfg(feature = "trash")] self.add_internal(open_trash) .with_shortcut("ot"); #[cfg(feature = "trash")] self.add_internal(restore_trashed_file) .with_shortcut("rt"); #[cfg(feature = "trash")] self.add_internal(delete_trashed_file) .with_shortcut("dt"); #[cfg(feature = "trash")] self.add_internal(purge_trash) .with_shortcut("et"); #[cfg(unix)] self.add_internal(filesystems) .with_shortcut("fs"); self.add_internal(focus_staging_area_no_open); // :focus is also hardcoded on Enter on directories // but ctrl-f is useful for focusing on a file's parent // (and keep the filter) self.add_internal(focus) .with_key(key!(L)) // hum... why this one ? .with_key(key!(ctrl-f)); self.add_internal(help) .with_key(key!(F1)) .with_shortcut("?"); #[cfg(feature="clipboard")] self.add_internal(input_paste) .with_key(key!(ctrl-v)); #[cfg(unix)] self.add_external( "mkdir {subpath}", "mkdir -p {subpath:path-from-directory}", StayInBroot, ) .with_shortcut("md"); #[cfg(windows)] self.add_external( "mkdir {subpath}", "cmd /c mkdir {subpath:path-from-directory}", StayInBroot, ) .with_shortcut("md"); #[cfg(unix)] self.add_external( "move {newpath}", "mv {file} {newpath:path-from-parent}", StayInBroot, ) .with_shortcut("mv"); #[cfg(windows)] self.add_external( "move {newpath}", "cmd /c move /Y {file} {newpath:path-from-parent}", StayInBroot, ) .with_shortcut("mv"); #[cfg(unix)] self.add_external( "move_to_panel", "mv {file} {other-panel-directory}", StayInBroot, ) .with_shortcut("mvp"); #[cfg(windows)] self.add_external( "move_to_panel", "cmd /c move /Y {file} {other-panel-directory}", StayInBroot, ) .with_shortcut("mvp"); #[cfg(unix)] self.add_external( "rename {new_filename:file-name}", "mv {file} {parent}/{new_filename}", StayInBroot, ) .with_auto_exec(false) .with_key(key!(f2)); #[cfg(windows)] self.add_external( "rename {new_filename:file-name}", "cmd /c move /Y {file} {parent}/{new_filename}", StayInBroot, ) .with_auto_exec(false) .with_key(key!(f2)); self.add_internal_bang(start_end_panel) .with_key(key!(ctrl-p)); // the char keys for mode_input are handled differently as they're not // consumed by the command self.add_internal(mode_input) .with_key(key!(' ')) .with_key(key!(':')) .with_key(key!('/')); self.add_internal(previous_match) .with_key(key!(shift-backtab)) .with_key(key!(backtab)); self.add_internal(next_match) .with_key(key!(tab)); self.add_internal(no_sort) .with_shortcut("ns"); self.add_internal(open_stay) .with_key(key!(enter)) .with_shortcut("os"); self.add_internal(open_stay_filter) .with_shortcut("osf"); self.add_internal(parent) .with_key(key!(h)) .with_shortcut("p"); self.add_internal(page_down) .with_key(key!(ctrl-d)) .with_key(key!(pagedown)); self.add_internal(page_up) .with_key(key!(ctrl-u)) .with_key(key!(pageup)); self.add_internal(panel_left_no_open) .with_key(key!(ctrl-left)); self.add_internal(panel_right) .with_key(key!(ctrl-right)); self.add_internal(print_path).with_shortcut("pp"); self.add_internal(print_relative_path).with_shortcut("prp"); self.add_internal(print_tree).with_shortcut("pt"); self.add_internal(quit) .with_key(key!(ctrl-c)) .with_key(key!(ctrl-q)) .with_shortcut("q"); self.add_internal(refresh).with_key(key!(f5)); self.add_internal(root_up) .with_key(key!(ctrl-up)); self.add_internal(root_down) .with_key(key!(ctrl-down)); self.add_internal(select_first); self.add_internal(select_last); self.add_internal(select); self.add_internal(show); self.add_internal(clear_stage).with_shortcut("cls"); self.add_internal(stage) .with_key(key!('+')); self.add_internal(unstage) .with_key(key!('-')); self.add_internal(stage_all_directories); self.add_internal(stage_all_files) .with_key(key!(ctrl-a)); self.add_internal(toggle_stage) .with_key(key!(ctrl-g)); self.add_internal(open_staging_area).with_shortcut("osa"); self.add_internal(close_staging_area).with_shortcut("csa"); self.add_internal(toggle_staging_area).with_shortcut("tsa"); self.add_internal(toggle_tree).with_shortcut("tree"); self.add_internal(sort_by_count).with_shortcut("sc"); self.add_internal(sort_by_date).with_shortcut("sd"); self.add_internal(sort_by_size).with_shortcut("ss"); self.add_internal(sort_by_type).with_shortcut("st"); #[cfg(unix)] self.add_external("rm", "rm -rf {file}", StayInBroot); #[cfg(windows)] self.add_external("rm", "cmd /c rmdir /Q /S {file}", StayInBroot) .with_condition(FileTypeCondition::Directory); #[cfg(windows)] self.add_external("rm", "cmd /c del /Q {file}", StayInBroot) .with_condition(FileTypeCondition::File); self.add_internal(toggle_counts).with_shortcut("counts"); self.add_internal(toggle_dates).with_shortcut("dates"); self.add_internal(toggle_device_id).with_shortcut("dev"); self.add_internal(toggle_files).with_shortcut("files"); self.add_internal(toggle_ignore) .with_key(key!(alt-i)) .with_shortcut("gi"); self.add_internal(toggle_git_file_info).with_shortcut("gf"); self.add_internal(toggle_git_status).with_shortcut("gs"); self.add_internal(toggle_root_fs).with_shortcut("rfs"); self.add_internal(set_max_depth); self.add_internal(unset_max_depth); self.add_internal(toggle_hidden) .with_key(key!(alt-h)) .with_shortcut("h"); #[cfg(unix)] self.add_internal(toggle_perm).with_shortcut("perm"); self.add_internal(toggle_sizes).with_shortcut("sizes"); self.add_internal(toggle_trim_root); self.add_internal(total_search); self.add_internal(search_again).with_key(key!(ctrl-s)); self.add_internal(up_tree).with_shortcut("up"); self.add_internal_with_args(move_panel_divider, "0 1").with_key(key!(alt-'>')); self.add_internal_with_args(move_panel_divider, "0 -1").with_key(key!(alt-'<')); self.add_internal(clear_output); self.add_internal(write_output); Ok(()) } fn build_add_internal( &mut self, internal: Internal, bang: bool, ) -> &mut Verb { let invocation = internal.invocation_pattern(); let execution = VerbExecution::Internal( InternalExecution::from_internal_bang(internal, bang) ); let description = VerbDescription::from_text(internal.description().to_string()); self.add_verb(Some(invocation), execution, description).unwrap() } fn add_internal( &mut self, internal: Internal, ) -> &mut Verb { self.build_add_internal(internal, false) } fn add_internal_with_args( &mut self, internal: Internal, args: &str, ) -> &mut Verb { let command = format!("{} {}", internal.name(), args); let execution = VerbExecution::Internal( InternalExecution { internal, bang: false, arg: Some(args.to_string()), } ); let description = VerbDescription::from_text(command.clone()); self.add_verb(Some(&command), execution, description).unwrap() } fn add_internal_bang( &mut self, internal: Internal, ) -> &mut Verb { self.build_add_internal(internal, true) } fn add_external( &mut self, invocation_str: &str, execution_str: &str, exec_mode: ExternalExecutionMode, ) -> &mut Verb { let execution = VerbExecution::External( ExternalExecution::new(ExecPattern::from_string(execution_str), exec_mode) ); self.add_verb( Some(invocation_str), execution, VerbDescription::from_code(execution_str.to_string()), ).unwrap() } pub fn add_verb( &mut self, invocation_str: Option<&str>, execution: VerbExecution, description: VerbDescription, ) -> Result<&mut Verb, ConfError> { let id = self.verbs.len(); self.verbs.push(Verb::new( id, invocation_str, execution, description, )?); Ok(&mut self.verbs[id]) } /// Create a verb from its configuration, adding it to its store pub fn add_from_conf( &mut self, vc: &VerbConf, ) -> Result<(), ConfError> { if vc.leave_broot == Some(false) && vc.from_shell == Some(true) { return Err(ConfError::InvalidVerbConf { details: "You can't simultaneously have leave_broot=false and from_shell=true".to_string(), }); } // we accept both key and keys. We merge both here let mut unchecked_keys = vc.keys.clone(); if let Some(key) = &vc.key { unchecked_keys.push(key.clone()); } let mut checked_keys = Vec::new(); for key in &unchecked_keys { let key = crokey::parse(key)?; if keys::is_reserved(key) { return Err(ConfError::ReservedKey { key: keys::KEY_FORMAT.to_string(key) }); } checked_keys.push(key); } let invocation = vc.invocation.clone().filter(|i| !i.is_empty()); let internal = vc.internal.as_ref().filter(|i| !i.is_empty()); let external = vc.external.as_ref().filter(|i| !i.is_empty()); let cmd = vc.cmd.as_ref().filter(|i| !i.is_empty()); let cmd_separator = vc.cmd_separator.as_ref().filter(|i| !i.is_empty()); let execution = vc.execution.as_ref().filter(|i| !i.is_empty()); let make_external_execution = |s| { let working_dir = match (vc.set_working_dir, &vc.working_dir) { (Some(false), _) => None, (_, Some(s)) => Some(s.clone()), (Some(true), None) => Some("{directory}".to_owned()), (None, None) => None, }; let mut external_execution = ExternalExecution::new( s, ExternalExecutionMode::from_conf(vc.from_shell, vc.leave_broot), ) .with_working_dir(working_dir); if let Some(b) = vc.switch_terminal { external_execution.switch_terminal = b; } external_execution }; let mut execution = match (execution, internal, external, cmd) { // old definition with "execution": we guess whether it's an internal or // an external (Some(ep), None, None, None) => { if let Some(internal_pattern) = ep.as_internal_pattern() { if let Some(previous_verb) = self.verbs.iter().find(|&v| v.has_name(internal_pattern)) { previous_verb.execution.clone() } else { VerbExecution::Internal(InternalExecution::try_from(internal_pattern)?) } } else { VerbExecution::External(make_external_execution(ep.clone())) } } // "internal": the leading `:` or ` ` is optional (None, Some(s), None, None) => { VerbExecution::Internal(if s.starts_with(':') || s.starts_with(' ') { InternalExecution::try_from(&s[1..])? } else { InternalExecution::try_from(s)? }) } // "external": it can be about any form (None, None, Some(ep), None) => { VerbExecution::External(make_external_execution(ep.clone())) } // "cmd": it's a sequence (None, None, None, Some(s)) => VerbExecution::Sequence(SequenceExecution { sequence: Sequence::new(s, cmd_separator), }), _ => { // there's no execution, this 'verbconf' is supposed to be dedicated to // unbind keys for key in checked_keys { self.unbound_keys.push(key); } return Ok(()); } }; if let Some(refresh_after) = vc.refresh_after { if let VerbExecution::External(external_execution) = &mut execution { external_execution.refresh_after = refresh_after; } else { warn!("refresh_after is only relevant for external commands"); } } let description = vc .description .clone() .map(VerbDescription::from_text) .unwrap_or_else(|| VerbDescription::from_code(execution.to_string())); let verb = self.add_verb( invocation.as_deref(), execution, description, )?; for extension in &vc.extensions { verb.file_extensions.push(extension.clone()); } if !checked_keys.is_empty() { verb.add_keys(checked_keys); } if let Some(shortcut) = &vc.shortcut { verb.names.push(shortcut.clone()); } if vc.auto_exec == Some(false) { verb.auto_exec = false; } if !vc.panels.is_empty() { verb.panels.clone_from(&vc.panels); } verb.selection_condition = vc.apply_to; Ok(()) } pub fn unbind_key( &mut self, key: KeyCombination, ) -> Result<(), ConfError> { debug!("unbinding key {:?}", key); for verb in &mut self.verbs { verb.keys.retain(|&k| k != key); } Ok(()) } pub fn unbind_name( &mut self, name: &str, ) -> Result<(), ConfError> { for verb in &mut self.verbs { verb.names.retain(|n| n != name); } Ok(()) } pub fn search_sel_info<'v>( &'v self, prefix: &str, sel_info: SelInfo<'_>, panel_state_type: Option, ) -> PrefixSearchResult<'v, &'v Verb> { self.search(prefix, Some(sel_info), true, panel_state_type) } pub fn search_prefix<'v>( &'v self, prefix: &str, panel_state_type: Option, ) -> PrefixSearchResult<'v, &'v Verb> { self.search(prefix, None, true, panel_state_type) } /// Return either the only match, or None if there's not /// exactly one match pub fn search_sel_info_unique <'v>( &'v self, prefix: &str, sel_info: SelInfo<'_>, panel_state_type: Option, ) -> Option<&'v Verb> { match self.search_sel_info(prefix, sel_info, panel_state_type) { PrefixSearchResult::Match(_, verb) => Some(verb), _ => None, } } pub fn search<'v>( &'v self, prefix: &str, sel_info: Option, short_circuit: bool, panel_state_type: Option, ) -> PrefixSearchResult<'v, &'v Verb> { let mut found_index = 0; let mut nb_found = 0; let mut completions: Vec<&str> = Vec::new(); let extension = sel_info.as_ref().and_then(|si| si.extension()); let sel_count = sel_info.map(|si| si.count_paths()); for (index, verb) in self.verbs.iter().enumerate() { if let Some(sel_info) = sel_info { if !sel_info.is_accepted_by(verb.selection_condition) { continue; } } if let Some(panel_state_type) = panel_state_type { if !verb.can_be_called_in_panel(panel_state_type) { continue; } } if let Some(count) = sel_count { if count > 1 && verb.is_sequence() { continue; } if count == 0 && verb.needs_selection { continue; } } if !verb.accepts_extension(extension) { continue; } for name in &verb.names { if name.starts_with(prefix) { if short_circuit && name == prefix { return PrefixSearchResult::Match(name, verb); } found_index = index; nb_found += 1; completions.push(name); continue; } } } match nb_found { 0 => PrefixSearchResult::NoMatch, 1 => PrefixSearchResult::Match(completions[0], &self.verbs[found_index]), _ => PrefixSearchResult::Matches(completions), } } pub fn key_desc_of_internal_stype( &self, internal: Internal, stype: SelectionType, ) -> Option { for verb in &self.verbs { if verb.get_internal() == Some(internal) && verb.selection_condition.accepts_selection_type(stype) { return verb.keys.first().map(|&k| KEY_FORMAT.to_string(k)); } } None } pub fn key_desc_of_internal( &self, internal: Internal, ) -> Option { for verb in &self.verbs { if verb.get_internal() == Some(internal) { return verb.keys.first().map(|&k| KEY_FORMAT.to_string(k)); } } None } pub fn verbs(&self) -> &[Verb] { &self.verbs } pub fn verb(&self, id: VerbId) -> &Verb { &self.verbs[id] } } #[test] fn check_builtin_verbs() { let mut conf = Conf::default(); let _store = VerbStore::new(&mut conf).unwrap(); } broot-1.46.3/src/verb/write.rs000064400000000000000000000022131046102023000142670ustar 00000000000000use { crate::{ app::*, errors::ProgramError, }, std::{ fs::{File, OpenOptions}, io::Write, }, }; /// Intended to verbs, this function writes the passed string to the file /// provided to broot with `--verb-output`, creating a new line if the /// file is not empty. pub fn verb_write( con: &AppContext, line: &str, ) -> Result { let Some(path) = &con.launch_args.verb_output else { return Ok(CmdResult::error("No --verb-output provided".to_string())); }; let mut file = OpenOptions::new() .create(true) .append(true) .open(path)?; if file.metadata().map(|m| m.len() > 0).unwrap_or(false) { writeln!(file)?; } write!(file, "{}", line)?; Ok(CmdResult::Keep) } /// Remove the content of the file provided to broot with `--verb-output`. pub fn verb_clear_output( con: &AppContext, ) -> Result { let Some(path) = &con.launch_args.verb_output else { return Ok(CmdResult::error("No --verb-output provided".to_string())); }; File::create(path)?; Ok(CmdResult::Keep) } broot-1.46.3/tests/search_strings.rs000064400000000000000000000054701046102023000156000ustar 00000000000000//! This checks some edge cases of pattern searches, especially composite patterns. //! Don't hesitate to suggest more tests for clarification or to prevent regressions. use { broot::{ command::CommandParts, pattern::*, }, }; fn build_pattern(s: &str) -> Pattern { let cp = CommandParts::from(s); let search_modes = SearchModeMap::default(); cp.pattern.print_tree(); Pattern::new( &cp.pattern, &search_modes, 0, // we don't do content search here ).unwrap() } fn check( pattern: &str, haystack: &str, expected: bool, ) { println!("applying pattern {:?} on {:?}", pattern, haystack); let pattern = build_pattern(pattern); //dbg!(&pattern); let found = pattern.search_string(haystack).is_some(); assert_eq!(found, expected); } #[test] fn simple_fuzzy() { check("toto", "toto", true); check("toto", "Toto", true); check("toto", "ToTuTo", true); check("tota", "ToTuTo", false); } #[test] fn simple_exact() { check("e/toto", "toto", true); check("e/toto", "Toto", false); check("e/toto", "Tototo", true); } #[test] fn simple_regex() { check("/toto", "toto", true); check("/to{3,5}to", "toto", false); check("/to{3,5}to", "tooooto", true); check("/to{3,5}to", "toooooooto", false); check("/to{3,5}to", "tooOoto", false); check("/to{3,5}to/i", "tooOoto", true); } #[test] fn one_operator() { check("a&b", "a", false); check("a&b", "ab", true); check("a|b", "ab", true); check("a|b", "b", true); check("a|b", "c", false); } #[test] fn negation() { check("!ab", "a", true); check("!ab", "aB", false); check("a&!b", "aB", false); check("a&!b", "aA", true); check("!a&!b", "ccc", true); check("!a|!b", "ccc", true); check("!a|!b", "cac", true); check("!a|!b", "cbc", true); check("!a|!b", "cbac", false); } // remember: it's left to right #[test] fn multiple_operators_no_parenthesis() { check("ab|ac|ad", "ab", true); check("ab|ac|ad", "ac", true); check("ab|ac|ad", "ad", true); check("ab|ac|ad|af|ag|er", "ad", true); check("ab&ac&ad", "ad", false); check("ab&ac&ad", "abcd", true); check("ab|ac|ad|ae", "ad", true); check("ab|ac|ad&ae", "ad", false); check("ab|ac|ad&ae", "axcd", false); check("ab|ac|ad&ae", "abe", true); check("ab|ac&ad|ae", "abd", true); check("ab|ac&ad|ae", "abc", false); } #[test] fn multiple_operators_with_parenthesis() { check("ab|(ac|ad)", "ab", true); check("(ab|ac)|ad", "ac", true); check("ab|(ac|ad)|ae", "ad", true); check("ab|ac|(ad&ae)", "ac", true); check("ab|ac|(ad&ae)", "ad", false); check("(ab|ac)&(ad|ae)", "ad", false); check("!(ab|ac)&(ad|ae)", "ad", true); check("ab|(ac&ad|ae)", "abc", true); check("(ab|ac)&(ad|ae)", "abd", true); }