[
  {
    "path": ".github/CODEOWNERS",
    "content": "# See: https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners\n\n* @denisidoro\n.github/* @denisidoro\n\nshell/navi.plugin.ps1 @alexis-opolka\ndocs/* @alexis-opolka\ndocs/**/* @alexis-opolka\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "github: denisidoro\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: \"\"\nlabels: bug\nassignees: \"\"\n---\n\n**Describe the bug**\nA clear and concise description of what the bug is.\n\n**To Reproduce**\nSteps to reproduce the behavior:\n\n1. Go to '...'\n2. Click on '....'\n3. Scroll down to '....'\n4. See error\n\n**Expected behavior**\nA clear and concise description of what you expected to happen.\n\n**Screenshots**\nIf applicable, add screenshots to help explain your problem.\n\n**Versions:**\n\n- OS: [e.g. macOS, WSL ubuntu, ubuntu]\n- Shell Version [replace this text with the output of `sh --version`]\n\n**Additional context**\nAdd any other context about the problem here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: \"\"\nlabels: new feature\nassignees: \"\"\n---\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Describe alternatives you've considered**\nA clear and concise description of any alternative solutions or features you've considered.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".github/config.yml",
    "content": "# Configuration for new-issue-welcome - https://github.com/behaviorbot/new-issue-welcome\n\n# Comment to be posted to on first time issues\nnewIssueWelcomeComment: >\n  Thanks for opening your first issue here! In case you're facing a bug, please update navi to the latest version first. Maybe the bug is already solved! :)\n\n# Configuration for new-pr-welcome - https://github.com/behaviorbot/new-pr-welcome\n\n# Comment to be posted to on PRs from first time contributors in your repository\nnewPRWelcomeComment: >\n  Thanks for opening this pull request!\n\n# Configuration for first-pr-merge - https://github.com/behaviorbot/first-pr-merge\n\n# Comment to be posted to on pull requests merged by a first time user\nfirstPRMergeComment: >\n  Congrats on merging your first pull request!\n"
  },
  {
    "path": ".github/workflows/cd.yml",
    "content": "name: Publish\n\non:\n  push:\n    tags:\n      - \"*\"\n  release:\n\njobs:\n  binary:\n    name: Publish ${{ matrix.target }}\n    runs-on: ${{ matrix.os }}\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n          - os: macos-latest\n            target: x86_64-apple-darwin\n          - os: ubuntu-latest\n            target: x86_64-unknown-linux-musl\n          - os: ubuntu-latest\n            target: x86_64-pc-windows-gnu\n          - os: ubuntu-latest\n            target: armv7-unknown-linux-musleabihf\n          - os: ubuntu-latest\n            target: armv7-linux-androideabi\n          - os: ubuntu-latest\n            target: aarch64-linux-android\n          - os: ubuntu-latest\n            target: aarch64-unknown-linux-gnu\n          - os: macos-latest\n            target: aarch64-apple-darwin\n    steps:\n      ### We're checking out the repository at the triggered ref\n      - uses: actions/checkout@v4\n\n      - name: Get the version\n        id: get_version\n        run: echo \"VERSION=${GITHUB_REF#refs/tags/}\" >> $GITHUB_OUTPUT\n\n      - name: Check if release exists\n        id: check_release\n        run: |\n          if gh release view ${{ steps.get_version.outputs.VERSION }} > /dev/null 2>&1; then\n            echo \"RELEASE_EXISTS=true\" >> $GITHUB_OUTPUT\n          else\n            echo \"RELEASE_EXISTS=false\" >> $GITHUB_OUTPUT\n          fi\n        env:\n          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Create release\n        continue-on-error: true\n        if: steps.check_release.outputs.RELEASE_EXISTS == 'false'\n        run: |\n          gh release create ${{ steps.get_version.outputs.VERSION }} \\\n            --title \"Release ${{ steps.get_version.outputs.VERSION }}\" \\\n            --notes \"Release notes for ${{ steps.get_version.outputs.VERSION }}\" \\\n        env:\n          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Build\n        id: build\n        run: scripts/release ${{ matrix.target }}\n\n      - name: Upload binaries to release\n        run: |\n          cd ./target/${{ matrix.target }}/release/\n          cp navi.${{ steps.build.outputs.EXTENSION }} navi-${{ steps.get_version.outputs.VERSION }}-${{ matrix.target }}.${{ steps.build.outputs.EXTENSION }}\n          gh release upload ${{ steps.get_version.outputs.VERSION }} navi-${{ steps.get_version.outputs.VERSION }}-${{ matrix.target }}.${{ steps.build.outputs.EXTENSION }}\n        env:\n          GH_TOKEN: ${{ github.token }}\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "# Based on https://github.com/actions-rs/meta/blob/master/recipes/quickstart.md\n#\n# While our \"example\" application has the platform-specific code,\n# for simplicity we are compiling and testing everything on the Ubuntu environment only.\n# For multi-OS testing see the `cross.yml` workflow.\n\non:\n  push:\n  pull_request:\n    branches: [master]\n\nname: CI\n\njobs:\n  # check:\n  #   name: Check\n  #   runs-on: ubuntu-latest\n  #   steps:\n  #     - name: Checkout sources\n  #       uses: actions/checkout@v2\n\n  #     - name: Install stable toolchain\n  #       uses: actions-rs/toolchain@v1\n  #       with:\n  #         profile: minimal\n  #         toolchain: stable\n  #         override: true\n\n  #     - name: Run cargo check\n  #       uses: actions-rs/cargo@v1\n  #       continue-on-error: false\n  #       with:\n  #         command: check\n\n  test:\n    name: Tests\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout sources\n        uses: actions/checkout@v2\n\n      - name: Install stable toolchain\n        uses: actions-rs/toolchain@v1\n        with:\n          profile: minimal\n          toolchain: stable\n          override: true\n\n      - name: Prep environment to test compiled-in paths\n        run: |\n          mkdir /tmp/cheats-dir\n          touch /tmp/config-file\n\n      - name: Run cargo test\n        uses: actions-rs/cargo@v1\n        continue-on-error: false\n        env:\n          NAVI_PATH: /tmp/cheats-dir\n          NAVI_CONFIG: /tmp/config-file\n        with:\n          command: test\n\n      - name: Run cargo test\n        uses: actions-rs/cargo@v1\n        continue-on-error: false\n        with:\n          command: test\n\n      - name: Install deps\n        run: ./scripts/dot pkg add git bash npm tmux\n\n      - name: Install fzf\n        run: git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf; yes | ~/.fzf/install;\n\n      - name: Install tealdeer\n        run: sudo npm install -g tldr\n\n      - name: Run bash tests\n        run: ./tests/run\n\n  lints:\n    name: Lints\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout sources\n        uses: actions/checkout@v2\n\n      # - name: Install stable toolchain\n      #   uses: actions-rs/toolchain@v1\n      #   with:\n      #     profile: minimal\n      #     toolchain: stable\n      #     override: true\n      #     components: rustfmt, clippy\n\n      - name: Run cargo fmt\n        uses: actions-rs/cargo@v1\n        continue-on-error: false\n        with:\n          command: fmt\n          args: --all -- --check\n\n      - name: Run cargo clippy\n        uses: actions-rs/cargo@v1\n        continue-on-error: false\n        with:\n          command: clippy\n          args: -- -D warnings\n"
  },
  {
    "path": ".gitignore",
    "content": "/target\n**/*.rs.bk\nnavi.log\n"
  },
  {
    "path": "Cargo.toml",
    "content": "[package]\nname = \"navi\"\nversion = \"2.25.0-beta1\"\nauthors = [\"Denis Isidoro <denis_isidoro@live.com>\", \"Alexis Opolka <alexis.opolka@protonmail.com>\"]\nedition = \"2021\"\ndescription = \"An interactive cheatsheet tool for the command-line\"\nhomepage = \"https://github.com/denisidoro/navi\"\ndocumentation = \"https://github.com/denisidoro/navi\"\nrepository = \"https://github.com/denisidoro/navi\"\nkeywords = [\"cheatsheets\", \"terminal\", \"cli\", \"tui\", \"shell\"]\ncategories = [\"command-line-utilities\"]\nlicense = \"Apache-2.0\"\n\n[features]\ndisable-command-execution = []\ndisable-repo-management = []\n\n[badges]\ntravis-ci = { repository = \"denisidoro/navi\", branch = \"master\" }\n\n[dependencies]\nregex = { version = \"1.7.3\", default-features = false, features = [\n   \"std\",\n   \"unicode-perl\",\n] }\nclap = { version = \"4.2.1\", features = [\"derive\", \"cargo\"] }\ncrossterm = \"0.28.0\"\nlazy_static = \"1.4.0\"\netcetera = \"0.10.0\"\nwalkdir = \"2.3.3\"\nshellwords = \"1.1.0\"\nanyhow = \"1.0.70\"\nthiserror = \"2.0.0\"\nstrip-ansi-escapes = \"0.2.0\"\nedit = \"0.1.4\"\nremove_dir_all = \"1.0.0\"\nserde = { version = \"1.0.219\", features = [\"derive\"] }\nserde_yaml = \"0.9.21\"\nunicode-width = \"0.2.0\"\ntracing = \"0.1.41\"\ntracing-subscriber = { version = \"0.3.19\", features = [\"env-filter\"] }\n\n[target.'cfg(windows)'.dependencies]\ndunce = \"1\"\n\n[lib]\nname = \"navi\"\npath = \"src/lib.rs\"\n\n[[bin]]\nname = \"navi\"\npath = \"src/bin/main.rs\"\nbench = false\n"
  },
  {
    "path": "LICENSE",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License."
  },
  {
    "path": "Makefile",
    "content": "export PATH := /usr/local/opt/bash/bin/:$(PATH)\n\ninstall:\n\tscripts/make install\n\nuninstall:\n\tscripts/make uninstall\n\nfix:\n\tscripts/make fix\n\ntest:\n\tscripts/test\n\nbuild:\n\tcargo build"
  },
  {
    "path": "README.md",
    "content": "# navi <img src=\"https://raw.githubusercontent.com/denisidoro/navi/master/assets/icon.png\" alt=\"icon\" height=\"28px\"/> [![Actions Status](https://github.com/denisidoro/navi/workflows/CI/badge.svg)](https://github.com/denisidoro/navi/actions) ![GitHub release](https://img.shields.io/github/v/release/denisidoro/navi?include_prereleases)\n\nAn interactive cheatsheet tool for the command-line.\n\n[![Demo](https://asciinema.org/a/406461.svg)](https://asciinema.org/a/406461)\n\n**navi** allows you to browse through cheatsheets (that you may write yourself or download from maintainers) and execute commands. Suggested values for arguments are dynamically displayed in a list.\n\n## Pros\n\n- it will spare you from knowing CLIs by heart\n- it will spare you from copy-pasting output from intermediate commands\n- it will make you type less\n- it will teach you new one-liners\n\nIt uses [fzf](https://github.com/junegunn/fzf) or [skim](https://github.com/lotabout/skim) under the hood and it can be either used as a command or as a shell widget (_à la_ Ctrl-R).\n\n## Table of contents\n\n- [Installation](#installation)\n- [Usage](#usage)\n- [Cheatsheet repositories](#cheatsheet-repositories)\n- [Cheatsheet syntax](#cheatsheet-syntax)\n- [Customization](#customization)\n- [More info](#more-info)\n\n## Installation\n\nThe recommended way to install **navi** is by running:\n\n```sh\nbrew install navi\n```\n\n> [!NOTE]\n> For more details on how to install Navi, see [docs/installation](docs/installation/README.md)\n\n**navi** can be installed with the following package managers:\n\n[![Packaging status](https://repology.org/badge/vertical-allrepos/navi.svg)](https://repology.org/project/navi/versions)\n\n## Usage\n\nThere are multiple ways to use **navi**:\n\n- by typing `navi` in the terminal\n  - pros: you have access to all possible subcommands and flags\n- as a [shell widget](docs/widgets/README.md#installing-the-shell-widget) for the terminal\n  - pros: the shell history is correctly populated (i.e. with the actual command you ran instead of `navi`) and you can edit the command as you wish before executing it\n- as a [Tmux widget](docs/widgets/howto/TMUX.md)\n  - pros: you can use your cheatsheets in any command-line app even in SSH sessions\n- as [aliases](docs/cheatsheet/syntax/README.md#aliases)\n- as a [shell scripting tool](docs/usage/shell-scripting/README.md)\n\nIn particular, check [these instructions](https://github.com/denisidoro/navi/issues/491) if you want to replicate what's shown in the demo above.\n\n## Cheatsheet repositories\n\nRunning **navi** for the first time will help you download and manage cheatsheets. By default, they are stored at `~/.local/share/navi/cheats/`.\n\nYou can also:\n\n- [browse through featured cheatsheets](docs/usage/commands/repo/README.md#browsing-through-cheatsheet-repositories)\n- [import cheatsheets from git repositories](docs/cheatsheet/repositories/README.md#importing-cheatsheet-repositories)\n- [write your own cheatsheets](#cheatsheet-syntax) (and [share them](docs/cheatsheet/repositories/README.md#submitting-cheatsheets), if you want)\n- [use cheatsheets from other tools](docs/cheatsheet/README.md#using-cheatsheets-from-other-tools), such as [tldr](https://github.com/tldr-pages/tldr) and [cheat.sh](https://github.com/chubin/cheat.sh)\n- [auto-update repositories](docs/cheatsheet/repositories/README.md#auto-updating-repositories)\n- auto-export cheatsheets from your [TiddlyWiki](https://tiddlywiki.com/) notes using a [TiddlyWiki plugin](https://bimlas.github.io/tw5-navi-cheatsheet/)\n\n## Cheatsheet syntax\n\nCheatsheets are described in `.cheat` files that look like this:\n\n```sh\n% git, code\n\n# Change branch\ngit checkout <branch>\n\n$ branch: git branch | awk '{print $NF}'\n```\n\nThe full syntax and examples can be found [here](docs/cheatsheet/syntax/README.md).\n\n## Customization\n\nYou can:\n\n- [setup your own config file](docs/configuration/README.md)\n- [set custom paths for your config file and cheat sheets](docs/configuration/README.md#paths-and-environment-variables)\n- [change colors](docs/configuration/README.md#changing-colors)\n- [resize columns](docs/configuration/README.md#resizing-columns)\n- [change how search is performed](docs/configuration/README.md#overriding-fzf-options)\n\n## More info\n\nPlease run the following command to read more about all possible options:\n\n```sh\nnavi --help\n```\n\nIn addition, please check the [/docs](docs) folder or the website.\n"
  },
  {
    "path": "docs/README.md",
    "content": "# Navi <img src=\"https://raw.githubusercontent.com/denisidoro/navi/master/assets/icon.png\" alt=\"icon\" height=\"28px\"/> [![Actions Status](https://github.com/denisidoro/navi/workflows/CI/badge.svg)](https://github.com/denisidoro/navi/actions) ![GitHub release](https://img.shields.io/github/v/release/denisidoro/navi?include_prereleases)\r\n\r\n## Table of Contents\r\n\r\n<!-- TOC -->\r\n* [Navi <img src=\"https://raw.githubusercontent.com/denisidoro/navi/master/assets/icon.png\" alt=\"icon\" height=\"28px\"/> ![Actions Status](https://github.com/denisidoro/navi/workflows/CI/badge.svg) ![GitHub release](https://img.shields.io/github/v/release/denisidoro/navi?include_prereleases)](#navi-img-srchttpsrawgithubusercontentcomdenisidoronavimasterassetsiconpng-alticon-height28px--)\r\n  * [Table of Contents](#table-of-contents)\r\n  * [About](#about)\r\n  * [Navi Pros](#navi-pros)\r\n  * [Similar tools](#similar-tools)\r\n  * [Etymology](#etymology)\r\n<!-- TOC -->\r\n\r\n## About\r\n\r\nNavi is an interactive cheatsheet tool for the command-line.\\\r\nIt allows you to browse through cheatsheets (that you may write yourself or download from maintainers) and execute commands.\r\n\r\n[![Demo](https://asciinema.org/a/406461.svg)](https://asciinema.org/a/406461)\r\n\r\nIt uses [fzf](https://github.com/junegunn/fzf), [skim](https://github.com/lotabout/skim), or [Alfred](https://www.alfredapp.com/) under the hood and it can be either used as a command or as a shell widget (_à la_ Ctrl-R).\r\n\r\n## Navi Pros\r\n\r\n- it will spare you from knowing CLIs by heart\r\n- it will spare you from copy-pasting output from intermediate commands\r\n- it will make you type less\r\n- it will teach you new one-liners\r\n\r\n## Similar tools\r\n\r\nThere are many similar projects out there ([beavr](https://github.com/denisidoro/beavr), [bro](https://github.com/hubsmoke/bro), [cheat](https://github.com/cheat/cheat), [cheat.sh](https://github.com/chubin/cheat.sh), [cmdmenu](https://github.com/amacfie/cmdmenu), [eg](https://github.com/srsudar/eg), [how2](https://github.com/santinic/how2), [howdoi](https://github.com/gleitz/howdoi), [Command Line Interface Pages](https://github.com/command-line-interface-pages) and [tldr](https://github.com/tldr-pages/tldr), to name a few).\r\n\r\nThey are excellent projects, but **navi** remains unique in the following ways:\r\n\r\n- it's natural to write cheatsheets tailored to your needs\r\n- arguments are neither hardcoded nor a simple template\r\n\r\n## Etymology\r\n\r\n[Navi](https://zelda.gamepedia.com/Navi) is a character from [The Legend of Zelda Ocarina of Time](https://zelda.gamepedia.com/Ocarina_of_Time) that provides [Link](https://zelda.gamepedia.com/Link) with a variety of clues to help him solve puzzles and make progress in his quest.\r\n"
  },
  {
    "path": "docs/cheatsheet/README.md",
    "content": "# Navi cheatsheets\n\n<!-- TOC -->\n* [Navi cheatsheets](#navi-cheatsheets)\n  * [Working with `cheatsheet repositories`](#working-with-cheatsheet-repositories)\n  * [Manually adding cheatsheets to navi](#manually-adding-cheatsheets-to-navi)\n  * [Choosing between queries and selection with variables](#choosing-between-queries-and-selection-with-variables)\n  * [Using cheatsheets from other tools](#using-cheatsheets-from-other-tools)\n<!-- TOC -->\n\n## Working with `cheatsheet repositories`\n\nNavi works best with what we call `cheatsheet repositories`, for more details see [cheatsheet/repositories](repositories/README.md).\n\n## Manually adding cheatsheets to navi\n\nIf you don't want to work with `cheatsheet repositories`, you can manually add your\ncheatsheets to navi by putting them into the `cheats_path` of your platform.\n\nYou can find out your path using the [info](/docs/usage/commands/info/README.md) subcommands\nbut a quick working command to go there would be:\n\n- Before 2.25.0\n\n    ```bash\n    cd $(navi info cheats-path)\n    ```\n\n- After 2.25.0\n\n    ```bash\n    cd $(navi info default-cheats-path)\n    ```\n\n## Choosing between queries and selection with variables\n\nNavi lets you use different methods to fill a variable value, when prompted.\n\n|    Keyboard key    |         Preference         |\n|:------------------:|:--------------------------:|\n|  <kbd> tab </kbd>  |   The query is preferred   |\n| <kbd> enter </kbd> | The selection is preferred |\n\nIt means if you enter the <kbd> tab </kbd> key, navi will let you enter the value.\n\n## Using cheatsheets from other tools\n\n> [!WARNING]\n> Navi **DOESN'T SUPPORT** as of now importing cheatsheets from other tools\n> but is able to **work with** TLDR and Cheat.sh.\n\n![Demo](https://user-images.githubusercontent.com/3226564/91878474-bae27500-ec55-11ea-8b19-17876178e887.gif)\n\nYou can use cheatsheets from [tldr](https://github.com/tldr-pages/tldr) by running:\n\n```sh\nnavi --tldr <query>\n```\n\nYou can use cheatsheets from [cheat.sh](https://github.com/chubin/cheat.sh) by running:\n\n```sh\nnavi --cheatsh <query>\n```\n"
  },
  {
    "path": "docs/cheatsheet/getting-started/README.md",
    "content": "# Cheatsheets - Getting started\n"
  },
  {
    "path": "docs/cheatsheet/repositories/README.md",
    "content": "# Cheatsheet repositories\r\n\r\n<!-- TOC -->\r\n* [Cheatsheet repositories](#cheatsheet-repositories)\r\n  * [About](#about)\r\n  * [Importing cheatsheet repositories](#importing-cheatsheet-repositories)\r\n  * [Submitting cheatsheets](#submitting-cheatsheets)\r\n  * [Auto-updating repositories](#auto-updating-repositories)\r\n<!-- TOC -->\r\n\r\n## About\r\n\r\nNavi lets you work with what we call `cheatsheet repositories`, they are git repositories\r\nand mainly consists of `.cheat` files.\r\n\r\nThis page is dedicated to the information you might need to work with `cheatsheet repositories`.\r\n\r\n## Importing cheatsheet repositories\r\n\r\nYou can import `cheatsheet repositories` with the `repo add` subcommand.\\\r\nSee [/docs/usage/commands/repo](/docs/usage/commands/repo/README.md#importing-cheatsheet-repositories) for more details.\r\n\r\n## Submitting cheatsheets\r\n\r\nThe featured repository for cheatsheets is [denisidoro/cheats](https://github.com/denisidoro/cheats),\r\nfeel free to open a PR[^1] there for me to include your contributions.\r\n\r\nIn order to add your own repository as a featured cheatsheet repo, please [edit this file](https://github.com/denisidoro/cheats/edit/master/featured_repos.txt) and open a PR[^1].\r\n\r\n## Auto-updating repositories\r\n\r\nRight now, **navi** doesn't have support for auto-updating out of the box.\r\nHowever, you can achieve this by using `git` and `crontab`.\r\n\r\n- First make sure you cloned your repo using `git` to the correct folder:\r\n\r\n  ```sh\r\n  user=\"<user>\"\r\n  repo=\"<repo>\"\r\n  git clone \"https://github.com/${user}/${repo}\" \"$(navi info cheats-path)/${user}__${repo}\"\r\n  ```\r\n\r\n- Then, add a cron job:\r\n\r\n  ```sh\r\n  crontab -e\r\n  */0 11 * * * bash -c 'cd \"$(/usr/local/bin/navi info cheats-path)/<user>__<repo>\" && /usr/local/bin/git pull -q origin master'\r\n  ```\r\n\r\n> [!NOTE]\r\n> Please note the cron job above is just an example **AND** you should edit it accordingly:\r\n>\r\n>- In this example, the cron job is triggered every day at 11am.\r\n>  \r\n>    You might want to check out [crontab guru](https://crontab.guru/) regarding crontab.\r\n>\r\n>- The full paths to `navi` and `git` may differ in your setup.\r\n>\r\n>    Check their actual values using `which` as `which <program>`.\r\n>\r\n>- Don't forget to replace `<user>__<repo>` with the actual folder name\r\n\r\n[^1]: A *PR* is short for Pull Request\r\n"
  },
  {
    "path": "docs/cheatsheet/syntax/README.md",
    "content": "# The syntax of a Navi cheatsheet\n\n<!-- TOC -->\n* [The syntax of a Navi cheatsheet](#the-syntax-of-a-navi-cheatsheet)\n  * [Syntax overview](#syntax-overview)\n  * [Variables](#variables)\n    * [Advanced variable options](#advanced-variable-options)\n    * [Variable dependency](#variable-dependency)\n      * [Implicit dependencies](#implicit-dependencies)\n      * [Explicit dependencies](#explicit-dependencies)\n    * [Variable as multiple arguments](#variable-as-multiple-arguments)\n  * [Extending cheats](#extending-cheats)\n  * [Multiline commands/snippets](#multiline-commandssnippets)\n  * [Aliases](#aliases)\n<!-- TOC -->\n\n## Syntax overview\n\nCheats are described in cheatsheet files.\\\nA cheatsheet is a file that has a `.cheat` or `.cheat.md` extension and looks like this:\n\n```sh\n% git, code\n\n# Change branch\ngit checkout <branch>\n\n$ branch: git branch | awk '{print $NF}'\n```\n\nA cheatsheet can have the following elements:\n\n|             Element              | Syntax |                                                                                       Description                                                                                        |\n|:--------------------------------:|:------:|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|\n|       Tags as cheat titles       |  `%`   |                                       Lines starting with this character are considered the start of a new cheat command and should contain tags.                                        |\n|        Cheat Description         |  `#`   |                                                Lines starting with this character should be the description of the cheat you're writing.                                                 |\n| Cheat Comments (or Metacomments) |  `;`   |                                          Lines starting with this character will be ignored by navi but they can be great as editor's comments.                                          |\n|      Pre-defined variables       |  `$`   |   Lines starting with this character should contain commands that generate a list of possible values. <br/> <br/> :information_source: See [#variables](#variables) for more details.    |\n|         Extended cheats          |  `@`   | Lines starting with this character should contain tags associated to other defined cheats. <br/> <br/> :information_source: See [#extending-cheats](#extending-cheats) for more details. |\n|       Executable commands        |  N/A   |                                                             All other non-empty lines are considered as executable commands.                                                             |\n\n> [!TIP]\n> If you are editing cheatsheets in Visual Studio Code, you could enable syntax highlighting\n> by installing this extension: [@yanivmo/navi-cheatsheet-language](https://marketplace.visualstudio.com/items?itemName=yanivmo.navi-cheatsheet-language).\n\n## Variables\n\nVariables are defined with brackets inside executable commands (e.g. `<branch>`).\\\nVariable names should only include alphanumeric characters and `_`.\n\nYou can show suggestions by using the Pre-defined variable lines (i.e. lines starting with`$`).\\\nOtherwise, the user will be able to type any value for it.\n\n### Advanced variable options\n\nFor Pre-Defined variable lines, you can use `---` to customize the behavior of `fzf`\nor how the value is going to be used.\n\nBelow are examples of such customization:\n\n- We define what column to use, the number of header lines and a delimiter between values.\n\n    ```sh\n    # This will pick the 3rd column and use the first line as header\n    docker rmi <image_id>\n    \n    $ image_id: docker images --- --column 3 --header-lines 1 --delimiter '\\s\\s+'\n    ```\n\n- We modify the output values of a command\n\n    ```shell\n    # Even though \"false/true\" is displayed, this will print \"0/1\"\n    echo <mapped>\n\n    $ mapped: echo 'false true' | tr ' ' '\\n' --- --map \"grep -q t && echo 1 || echo 0\"\n    ```\n\n\nThe supported parameters are:\n\n| Parameter               | Description                                                                               |\n|:------------------------|:------------------------------------------------------------------------------------------|\n| `--column <number>`     | `<number>` is the column number to extract from the result.                               |\n| `--map <bash_code>`     | **_[EXPERIMENTAL]_** `<bash_code>` is a map function to apply to the variable value.      |\n| `--prevent-extra`       | **_[EXPERIMENTAL]_** This parameter will limit the user to select one of the suggestions. |\n| `--fzf-overrides <arg>` | **_[EXPERIMENTAL]_** `<arg>` is an arbitrary argument to override `fzf` behaviour.        |\n| `--expand`              | **_[EXPERIMENTAL]_** This parameter will convert each line into a separate argument.      |\n\n\nIn addition, it's possible to forward the following parameters to `fzf`:\n\n| Parameter forwarded to `fzf` |\n|:-----------------------------|\n| `--multi`                    |\n| `--header-lines <number>`    |\n| `--delimiter <regex>`        |\n| `--query <text>`             |\n| `--filter <text>`            |\n| `--header <text>`            |\n| `--preview <bash_code>`      |\n| `--preview-window <text>`    |\n\n### Variable dependency\n\nPre-Defined variables can refer other pre-defined variables in two different ways, an implicit and explicit way.\n\n#### Implicit dependencies\n\nAn implicit dependency is when you refer another variable with the same syntax used in\nexecutable commands (i.e. `<variable>`).\n\nBelow is an example of using implicit dependencies to construct a path:\n\n```sh\n# Should print /my/pictures/wallpapers\necho \"<wallpaper_folder>\"\n\n$ pictures_folder: echo \"/my/pictures\"\n$ wallpaper_folder: echo \"<pictures_folder>/wallpapers\"\n```\n\n#### Explicit dependencies\n\nAn explicit dependency is when you prepend a dollar sign (i.e. `$`) to the variable name.\n\nBelow is an example of using explicit dependencies to give multiple choices:\n\n```sh\n# If you select \"hello\" for <x>, the possible values of <y> will be \"hello foo\" and \"hello bar\"\necho <x> <y>\n\n# If you want to ignore the contents of <x> and only print <y>\n: <x>; echo <y>\n\n$ x: echo \"hello hi\" | tr ' ' '\\n'\n$ y: echo \"$x foo;$x bar\" | tr ';' '\\n'\n```\n\n### Variable as multiple arguments\n\nVariables can have multiple arguments,\nbelow is an example of using multiple arguments to cat multiple files at the same time.\n\n```sh\n# This will result into: cat \"file1.json\" \"file2.json\"\ncat <jsons>\n\n$ jsons: find . -iname '*.json' -type f -print --- --multi --expand\n```\n\n## Extending cheats\n\nNavi allows you to extend a cheat context with `Extended cheats` lines (i.e. starting with `@`).\\\nIf you put the same tags from another cheat, you will be able to share the same context and will\nbe able to use the same variables, for example.\n\n```sh\n% dirs, common\n\n$ pictures_folder: echo \"/my/pictures\"\n\n% wallpapers\n@ dirs, common\n\n# Should print /my/pictures/wallpapers\necho \"<pictures_folder>/wallpapers\"\n\n% screenshots\n@ dirs, common\n\n# Should print /my/pictures/screenshots\necho \"<pictures_folder>/screenshots\"\n```\n\n## Multiline commands/snippets\n\nCommands can be multiline, we call them snippets.\n\n- You can write them as follows:\n\n    ```sh\n    % bash, foo\n\n    # This will output \"foo\\nyes\"\n    echo foo\n    true \\\n       && echo yes \\\n       || echo no\n    ```\n\n- Or, you can place them inside Markdown code blocks, delimited by triple backticks (```` ``` ````):\n\n    ````sh\n    % git, code\n    \n    # Change branch\n    ```sh\n    git checkout <branch>\n    ```\n    \n    $ branch: git branch | awk '{print $NF}'\n    ````\n\n\n## Aliases\n\n**navi** doesn't have support for aliases as first-class citizens at the moment.\\\nHowever, it is easy to create aliases using **navi** + a few conventions.\n\n> [!CAUTION]\n> The examples below will only work if you use **navi** as a shell scripting tool.\n>\n> See [/docs/usage/shell-scripting](/docs/usage/shell-scripting/README.md) for more details.\n\nFor example, suppose you decide to end some of your commands with `:: <some_alias>`:\n\n```bash\n% aliases\n\n# This is one command :: el\necho lorem ipsum\n\n# This is another command :: ef\necho foo bar\n```\n\nYou could add something similar to this in your `.bashrc`-like file:\n\n```bash\nnavialias() {\n    navi --query \":: $1\" --best-match\n}\n\nalias el=\"navialias el\"\nalias ef=\"navialias ef\"\n```\n\nIf you don't want to use these conventions, you can even add full comments in your aliases:\n\n```bash\nnavibestmatch() {\n    navi --query \"$1\" --best-match\n}\n\nalias el=\"navibestmatch 'This is one command'\"\nalias ef=\"navibestmatch 'This is another command'\"\n```\n"
  },
  {
    "path": "docs/configuration/README.md",
    "content": "# Configuring Navi\n\nNavi allows you to configure it with a YAML configuration.\n\n<!-- TOC -->\n* [Configuring Navi](#configuring-navi)\n  * [Paths and Environment Variables](#paths-and-environment-variables)\n    * [The default configuration file path](#the-default-configuration-file-path)\n    * [Cheatsheets paths](#cheatsheets-paths)\n      * [The default cheatsheets path](#the-default-cheatsheets-path)\n      * [Defining the cheatsheets path with the environment variable](#defining-the-cheatsheets-path-with-the-environment-variable)\n      * [Defining the cheatsheets path in the configuration file](#defining-the-cheatsheets-path-in-the-configuration-file)\n        * [[DEPRECATED] - Using the `path` directive](#deprecated---using-the-path-directive)\n  * [Customization](#customization)\n    * [Changing colors](#changing-colors)\n      * [fzf color scheme](#fzf-color-scheme)\n      * [Navi colors](#navi-colors)\n    * [Resizing columns](#resizing-columns)\n    * [Overriding fzf options](#overriding-fzf-options)\n      * [Overriding during cheats selection](#overriding-during-cheats-selection)\n      * [Overriding during values selection](#overriding-during-values-selection)\n      * [Overriding for all cases](#overriding-for-all-cases)\n  * [Defining your own delimiter](#defining-your-own-delimiter)\n<!-- TOC -->\n\n## Paths and Environment Variables\n\nOn the technical side, navi uses the `directories-next` crate for rust,\nwhich defines platform-specific locations to store the configuration files,\nthe cache and other types of files an application might need.\n\n> [!TIP]\n> For example, this is why cheatsheets are being stored in `~/Library/Application Support/navi` on macOS.\n\n> [!NOTE]\n> Interested on how `directories-next` works?\\\n> Go see their `crates.io` page: [crates.io/crates/directories-next](https://crates.io/crates/directories-next)\n\n\n### The default configuration file path\n\nDuring the compilation of navi, the default configuration file path is set by the `$NAVI_CONFIG` environment variable.\\\nIf it is not set, it fallbacks to `~/.config/navi/config.yaml`.\n\nYou can check your default configuration file path with the info subcommand,\nsee [/docs/usage/commands/info/](/docs/usage/commands/info/README.md#default-configuration-path) for more details.\n\n### Cheatsheets paths\n\nNavi checks the paths in the following order until it finds a value:\n\n1. the `$NAVI_PATH` environment variable\n2. the configuration file\n3. The default value of navi\n\n#### The default cheatsheets path\n\nBy default, navi stores the cheatsheets in the `~/.local/share/navi/cheats/` directory.\n\nYou can check your default cheatsheets path with the info subcommand,\nsee [/docs/usage/commands/info/](/docs/usage/commands/info/README.md#default-cheatsheets-path) for more details.\n\n#### Defining the cheatsheets path with the environment variable\n\nThe cheatsheets path can be defined using the `$NAVI_PATH` environment variable in a colon-separated list, for example:\n\n```sh\nexport NAVI_PATH='/path/to/a/dir:/path/to/another/dir:/yet/another/dir'\n```\n\n#### Defining the cheatsheets path in the configuration file\n\nYou can define the cheatsheets path in the configuration file with the following syntax:\n\n```yaml\ncheats:\n  paths:\n    - /path/to/some/dir # on unix-like os\n    - F:\\\\path\\\\to\\\\dir # on Windows\n```\n\n##### [DEPRECATED] - Using the `path` directive\n\nUntil `2.17.0`, you could define your cheatsheets path with the `path` directive with the following syntax:\n\n```yaml\ncheats:\n  path: /path/to/some/dir\n```\n\nThe directive is now deprecated and will be removed in `2.27.0`.\n\n## Customization\n\n### Changing colors\n\n#### fzf color scheme\n\nYou can change the color scheme of `fzf` by overriding fzf options.\n\n> [!NOTE]\n> See [@junegunn/fzf/wiki/Color-schemes](https://github.com/junegunn/fzf/wiki/Color-schemes) and\n> [#overriding-fzf-options](#overriding-fzf-options) for more details.\n\n#### Navi colors\n\nYou can change the text color for each column of navi in the configuration file with the following syntax:\n\n```yaml\nstyle:\n  tag:\n    color: <your color for tags>\n  comment:\n    color: <your color for comments>\n  snippet:\n    color: <your color for snippets>\n```\n\nBelow is an example of what to do if you'd like navi to look like the French flag:\n\n- `config.yaml`:\n\n  ```yaml\n  style:\n    tag:\n      color: blue\n    comment:\n      color: white\n    snippet:\n      color: red\n  ```\n\n- The result:\n\n  ![navi-custom-colors](https://github.com/user-attachments/assets/d80352c5-d888-43e6-927d-805a8de1a7e2)\n\n### Resizing columns\n\nYou can change the column width of each column of navi in the configuration file with the following syntax:\n\n```yaml\nstyle:\n  tag:\n    width_percentage: <width relative to the terminal window>\n    min_width: <width as number of characters>\n  comment:\n    width_percentage: <width relative to the terminal window>\n    min_width: <width as number of characters>\n  snippet:\n    width_percentage: <width relative to the terminal window>\n    min_width: <width as number of characters>\n```\n\n### Overriding fzf options\n\nYou can override fzf options for two different cases:\n\n- During the cheats selection\n\n  Navi exposes the `overrides` directive in the configuration file\n  and the `NAVI_FZF_OVERRIDES` environment variable.\n\n- During the pre-defined variable values selection\n\n  Navi exposes the `overrides_var` directive in the configuration file\n  and the `NAVI_FZF_OVERRIDES_VAR` environment variable.\n\nFor all cases, navi exposes the `FZF_DEFAULT_OPTS` environment variable.\n\n#### Overriding during cheats selection\n\nIf you want to do the override with `--height 3`,\nyou can do it with the following syntax in the configuration file:\n\n```yaml\nfinder:\n  command: fzf\n  overrides: --height 3\n```\n\nBut you can also define the environment variable like this:\n\n```bash\nexport NAVI_FZF_OVERRIDES='--height 3'\n```\n\n#### Overriding during values selection\n\nIf you want to do the override with `--height 3`,\nyou can do it with the following syntax in the configuration file:\n\n```yaml\nfinder:\n  command: fzf\n  overrides_var: --height 3\n```\n\nBut you can also define the environment variable like this:\n\n```bash\nexport NAVI_FZF_OVERRIDES_VAR='--height 3'\n```\n\n#### Overriding for all cases\n\nYou can define the environment variable like this:\n\n```bash\nexport FZF_DEFAULT_OPTS=\"--height 3\"\n```\n\n> [!NOTE]\n> See [@junegunn/fzf](https://github.com/junegunn/fzf#layout) for more details on `$FZF_DEFAULT_OPTS`.\n\n## Defining your own delimiter\n\nNavi allows you to define your own delimiter to parse the selected result for a variable in your cheats.\\\nIt is equivalent to defining `--delimiter` used with `--column`.\n\nYou can define it as such:\n\n```yaml\nfinder:\n  delimiter_var: <your-regex-delimiter> ### By default the expression is \\s\\s+\n```\n\n> [!CAUTION]\n> Defining the delimiter via the configuration file means that Navi will use this delimiter by default for\n> every variable using the `--column` instruction.\n\nYou can override this configuration with the `--delimiter` instruction in the variable definition of your cheat.\\\nSee [/docs/cheatsheet/syntax/](/docs/cheatsheet/syntax/README.md#advanced-variable-options) for more details.\n\n"
  },
  {
    "path": "docs/contributions/README.md",
    "content": "# Navi contributors\r\n\r\nThis section is about the ways you can contribute to Navi and its ecosystem.\r\n\r\n<!-- TOC -->\r\n* [Navi contributors](#navi-contributors)\r\n  * [How to contribute to Navi](#how-to-contribute-to-navi)\r\n  * [Versioning Scheme](#versioning-scheme)\r\n  * [Deprecation of features](#deprecation-of-features)\r\n<!-- TOC -->\r\n\r\n## How to contribute to Navi\r\n\r\nYou have multiple ways to contribute to navi, here are the documented ones:\r\n\r\n- [Write code for Navi](code/README.md)\r\n- [Write documentation for Navi](documentation/README.md)\r\n- [Open Bug tickets](bugs/README.md)\r\n\r\nPlease see each section for more details.\r\n\r\n\r\n## Versioning Scheme\r\n\r\n| Type  | Description                                                                                      |\r\n|-------|--------------------------------------------------------------------------------------------------|\r\n| Major | Anything which introduces a major breaking change. The bash to rust rewrite was such an example. |\r\n| Minor | Almost everything.                                                                               |\r\n| Fix   | A fix, just like its name. It should be micro releases with minimal changes.                     |\r\n\r\n## Deprecation of features\r\n\r\nOnce you introduce a feature, you need to have a clear view of when we're\r\ngoing to remove its support within navi.\r\n\r\nIn order to offer stability to the users, we prefer having 10 minor versions\r\nbetween the deprecation notice and the removal of its support.\r\n\r\n````txt\r\nVersion where the feature is being deprecated: 0.10.0\r\nVersion where the support is dropped: 0.20.0\r\n````\r\n\r\n> [!NOTE]\r\n> This rule is not absolute and each feature deprecation needs to be handled\r\n> carefully given its own circumstances, but try to stick as close as possible\r\n> to this rule.\r\n"
  },
  {
    "path": "docs/contributions/bugs/README.md",
    "content": "# Contribute in opening bug tickets\r\n\r\nLike any other software, navi has bugs.\r\n\r\nIf you encounter an issue with Navi, we encourage you to open a bug ticket.\\\r\nPlease see [https://github.com/denisidoro/navi/issues/](https://github.com/denisidoro/navi/issues/) to open an issue.\r\n"
  },
  {
    "path": "docs/contributions/code/README.md",
    "content": "# Contribute code to Navi\r\n\r\nNavi is written in Rust, the widgets may be written in any language given it can be integrated with Navi.\r\n\r\nIf you take the example of the most common widgets for Navi they are written in their shell scripting language\r\nbecause they intend to integrate Navi with the shell in question (Fish, Zsh, NuShell, PowerShell, etc.).\r\n\r\nWe separate Navi into two categories:\r\n\r\n- `Navi Core` which refers to Navi's code in Rust\r\n- `Navi Widgets` which refers to code that intends to integrate Navi with a 3rd-party software\r\n\r\n## Contribute to Navi Core\r\n\r\nIf you want to contribute to Navi Core there are certain steps you need to follow for\r\nyour changes to be accepted.\r\n\r\n1. First, open an issue if no opened issues are related to the change you want to contribute.\r\n2. [Optional] Wait to have an opinion from the maintainers, developers or contributors from Navi.\r\n\r\n   > This step is marked as *Optional* as you can open a Merge Request (MR)/Pull Request (PR)  \r\n   > without having to open an issue beforehand, although it is recommended to not do so.\r\n\r\n   We ask you to wait before working on a PR as the way you see a feature and its implementation\r\n   might not be similar on how a maintainer of Navi sees it.\r\n\r\n   This will save you and the maintainers time.\r\n\r\n3. Fork the repository and iterate over your changes.\r\n4. Update Navi documentation\r\n\r\n    If you implement a new feature, you will need to create a new entry in the project's\r\n    documentation for users to know what has changed.\r\n\r\n    No significant modification in Navi's behaviour should be merged without being documented.\\\r\n    For more details I recommend you to see [contributions/documentation/](../documentation/README.md).\r\n\r\n5. Open a PR on [denisidoro/navi](https://github.com/denisidoro/navi/pulls) and request a review\r\n6. [Optional] Your PR needs revisions and changes before it can be merged\r\n\r\n    It's not rare that your PR will need changes before it can be accepted by the maintainers\r\n    and then merged into the main branch.\r\n\r\n7. Your PR has been merged    \r\n\r\n    Congratulations! Your PR has been reviewed and merged, you should be proud of it,\r\n    and we thank you for your contribution.\r\n\r\n    The next release cycle will package all contributions into a new release and users\r\n    throughout the world will be able to use your new feature(s).\r\n"
  },
  {
    "path": "docs/contributions/documentation/README.md",
    "content": "# Contribute documentation to Navi\r\n\r\nIf you don't want or can't code in Rust, we welcome all contributions,\r\neven more so if it's related to documentation.\r\n\r\nThe documentation of Navi is currently made in Markdown.\r\n\r\n## Markdown documentation\r\n\r\nThe documentation source files are located in the `docs/` folder and are mainly grouped by features.\r\nThe current documentation follows a structure where one folder equals one topic.\r\n\r\nHere is a quick representation of the folder structure this documentation currently follows:\r\n\r\n```txt\r\n.\r\n+-- docs\r\n|   +-- examples\r\n|   |   +-- <topic-examples>\r\n|   +-- src\r\n|   |   +-- <topic-source-files>\r\n|   |   |   +-- <sorted-by-type>\r\n|   +-- <topic>\r\n|   |   +-- README.md\r\n```\r\n\r\nYou can see that we have separated the `src` and `examples` folder from the topic with the intent to make it\r\neasier to find each type of documentation.\r\n\r\n> [!NOTE]\r\n> It is recommended to not go deeper than 3 levels in the documentation.\r\n\r\n"
  },
  {
    "path": "docs/deprecated/Alfred/README.md",
    "content": "# Alfred \n\n> [!CAUTION]\n> This feature has been deprecated and support has been dropped since 2.16.0.\n>\n> The latest version with support is [2.15.1](https://github.com/denisidoro/navi/releases/tag/v2.15.1).\n\n\nThis is _experimental_. If you face any issues, please report [here](https://github.com/denisidoro/navi/issues/348).\n\n![Alfred demo](https://user-images.githubusercontent.com/3226564/80294838-582b1b00-8743-11ea-9eb5-a335d8eed833.gif)\n\n### Instructions\n\n- make sure you have [Alfred Powerpack](https://www.alfredapp.com/powerpack/)\n- make sure **navi** [2.15.1](https://github.com/denisidoro/navi/releases/tag/v2.15.1) is installed\n- make sure that the `navi` binary is in the `$PATH` determined by `~/.bashrc`\n- download and install the [latest .alfredworkflow available](https://github.com/denisidoro/navi/releases/tag/v2.15.1)\n"
  },
  {
    "path": "docs/examples/cheatsheet/example.cheat",
    "content": "% first cheat\r\n\r\n# print something\r\necho \"My name is <name>!\"\r\n\r\n$ name: whoami\r\n"
  },
  {
    "path": "docs/examples/cheatsheet/navi.cheat",
    "content": "% cheatsheets\n\t \n# Download default cheatsheets\nnavi repo add denisidoro/cheats\n\n# Browse for cheatsheet repos\nnavi repo browse\n\n# Edit main local cheatsheets\nf=\"$(navi info cheats-path)/main.cheat\"\n[ -f \"$f\" ] || navi info cheats-example > \"$f\"\n${EDITOR:-nano} \"$f\"\n\n\n% config\n\n# Edit config file\nf=\"$(navi info config-path)\"\n[ -f \"$f\" ] || navi info config-example > \"$f\"\n${EDITOR:-nano} \"$f\"\n\n\n% 3rd-party\n\n# Search using tldr\nnavi --tldr \"<query>\"\n\n# Search using cheatsh\nnavi --cheatsh \"<query>\"\n\n\n% widget\n\n# Load shell widget\nshell=\"$(basename $SHELL)\"; eval \"$(navi widget $shell)\"\n\n\n% help\n\n# Read command-line help text\nnavi --help\n\n# Read project README.md\nnavi fn url::open \"https://github.com/denisidoro/navi\"\n"
  },
  {
    "path": "docs/examples/configuration/config-example.yaml",
    "content": "# THIS IS EXPERIMENTAL\n# the config file schema may change at any time\n\nstyle:\n  tag:\n    color: cyan # text color. possible values: https://bit.ly/3gloNNI\n    width_percentage: 26 # column width relative to the terminal window\n    min_width: 20 # minimum column width as number of characters\n  comment:\n    color: blue\n    width_percentage: 42\n    min_width: 45\n  snippet:\n    color: white\n\nfinder:\n  command: fzf # equivalent to the --finder option\n  # overrides: --tac # equivalent to the --fzf-overrides option\n  # overrides_var: --tac # equivalent to the --fzf-overrides-var option\n  # delimiter_var: \\s\\s+ # equivalent to the --delimiter option that is used with --column option when you extract a column from the selected result for a variable\n\n# cheats:\n#   paths:\n#     - /path/to/some/dir # on unix-like os\n#     - F:\\\\path\\\\to\\\\dir # on Windows\n\n# search:\n#   tags: git,!checkout # equivalent to the --tag-rules option\n\n# client:\n#   tealdeer: true # enables tealdeer support for navi --tldr\n\nshell:\n  # Shell used for shell out. Possible values: bash, zsh, dash, ...\n  # For Windows, use `cmd.exe` instead.\n  command: bash\n\n  # finder_command: bash # similar, but for fzf's internals\n"
  },
  {
    "path": "docs/installation/README.md",
    "content": "# Installation of navi\n\nThis is a reference of all known methods to install navi.\n\n> [!CAUTION]\n> Navi, as of now, has only two official builds, the released binaries on GitHub\n> and the published package on brew.\n> \n> All the other packages are community-maintained.\n\n## Using package managers\n\n### Homebrew\n\n```sh\nbrew install navi\n```\n\n> [!NOTE]\n> See [brew.sh](https://brew.sh/) for more details.\n\n### Using Gentoo\n\n> [!WARNING]\n> You need to enable the GURU overlay for the instructions below to work correctly.\n> \n> For more details see:\n> \n> - [wiki.gentoo.org/wiki/Ebuild_repository](https://wiki.gentoo.org/wiki/Ebuild_repository)\n> - [gpo.zugaina.org/Overlays/guru/app-misc/navi](https://gpo.zugaina.org/Overlays/guru/app-misc/navi).\n\n```sh\nemerge -a app-misc/navi\n```\n\n> [!NOTE]\n> See [Gentoo.org](https://gentoo.org/) for more details.\n\n### Using Pacman\n\n```sh\npacman -S navi\n```\n\n> [!NOTE]\n> See [wiki.archlinux.org/title/Pacman](https://wiki.archlinux.org/title/Pacman) for more details.\n\n### Using nix\n\n```sh\nnix-env -iA nixpkgs.navi\n```\n\n> [!NOTE]\n> See [nixos.org](https://nixos.org/) for more details\n\n### Using Cargo\n\n```bash\ncargo install --locked navi\n```\n\n> [!NOTE]\n> See [@rust-lang/cargo](https://github.com/rust-lang/cargo) for more details.\n\n### Using Chocolatey\n\n```bash\nchoco install navi\n```\n\n> [!CAUTION]\n> You currently need to create the config file `$env:USERPROFILE\\AppData\\Roaming\\navi\\config.yaml`\n> and define the `shell.command` directive as `powershell` for navi to work correctly.\n> \n> ```yaml\n> shell:\n>   command: powershell\n> ```\n\n> [!NOTE]\n> See [community.chocolatey.org](https://community.chocolatey.org) for more details.\n\n## Using the installation script\n\nNavi has an installation script ready for you to use, you can call it like this:\n\n```bash\nbash <(curl -sL https://raw.githubusercontent.com/denisidoro/navi/master/scripts/install)\n```\n\nIf you need to define the directory for the binary, you can call it like this:\n\n```bash\nBIN_DIR=/usr/local/bin bash <(curl -sL https://raw.githubusercontent.com/denisidoro/navi/master/scripts/install)\n```\n\n## Downloading pre-compiled binaries\n\nWith each release, we try our best to build and publish a binary for each\nsupported platform, you can find them here:\n[@denisidoro/navi/releases/latest](https://github.com/denisidoro/navi/releases/latest)\n\nWhat you need to do is:\n\n- to download the binary corresponding to the version you want to install\n- to extract the content of the archive to your `$PATH`\n\n## Building from source\n\nYou can also build navi from source, it's mainly used by contributors to\ntest their modifications but can be used by end users who want to build their own version.\n\n- You need to clone the repository:\n\n    ```bash\n    git clone https://github.com/denisidoro/navi && cd navi\n    ```\n\n- Call `make`\n\n    ```bash\n    make install\n    ```\n\nYou can specify the binary directory with:\n\n```bash\nmake BIN_DIR=/usr/local/bin install\n```\n\n## Compile time environment variables\n\n**navi** supports environment variables at compile time that will modify the behavior of navi at runtime, they are:\n\n| Environment variable | Description                                                 |\n|----------------------|-------------------------------------------------------------|\n| `NAVI_PATH`          | This defines the default path used by navi for cheatsheets. |\n| `NAVI_CONFIG`        | This defines the default configuration file used by navi.   |\n\n## Other package managers\n\nYou can find **navi** for more package managers by clicking on the image below:\n\n[![Packaging status](https://repology.org/badge/vertical-allrepos/navi.svg)](https://repology.org/project/navi/versions)\n\nFeel free to be the maintainer of **navi** for any package manager you'd like!\n"
  },
  {
    "path": "docs/usage/README.md",
    "content": "# The usage of Navi\n\nNavi can be used in multiple ways\n\n#### Defining the cheatsheets path at runtime\n\nYou can define the paths to use for cheatsheets at runtime using the `--path` parameter and a colon-separated paths list\n\nFor example, if we want to search for cheatsheets in `/some/dir` and in `/other/dir`:\n\n```sh\nnavi --path '/some/dir:/other/dir'\n```\n\n## Logging\n\nThe log file will be created under the same directory where the configuration file is located.\\\nYou can use the `RUST_LOG` environment variable to set the log level.\n\nFor example, to have the logging in debug mode when running navi:\n\n```bash\nRUST_LOG=debug navi\n```\n\n> [!NOTE]\n> If the directory of the configuration file doesn't exist, no log file\n> is going to be created.\n"
  },
  {
    "path": "docs/usage/commands/info/README.md",
    "content": "# The info subcommands of navi\n\nNavi exposes information about its default values or examples for you to use.\n\n<!-- TOC -->\n* [The info subcommands of navi](#the-info-subcommands-of-navi)\n  * [Commands Reference](#commands-reference)\n  * [Default configuration information](#default-configuration-information)\n    * [Default configuration path](#default-configuration-path)\n    * [Example configuration file](#example-configuration-file)\n  * [Default cheatsheets path](#default-cheatsheets-path)\n<!-- TOC -->\n\n## Commands Reference\n\n| Command             | Description                                        |\n|---------------------|----------------------------------------------------|\n| config-path         | [DEPRECATED] Lets you see the default config path  |\n| cheats-path         | [DEPRECATED] Lets you see the default cheats path  |\n| default-config-path | Lets you see the default config path               |\n| default-cheats-path | Lets you see the default cheats path               |\n| config-example      | Lets you see an example for the configuration file |\n| cheats-example      | Lets you see an example for a cheat file           |\n\n## Default configuration information\n\n### Default configuration path\n\nNavi exposes its default configuration path with:\n\n```sh\nnavi info config-path\n```\n\n> [!NOTE]\n> See [/docs/configuration/](/docs/configuration/README.md#the-default-configuration-file-path) for more details on how the default configuration path is defined.\n\n### Example configuration file\n\nNavi lets you get an example configuration file with:\n\n```sh\nnavi info config-example\n```\n\n> [!NOTE]\n> You can retrieve this file at the following address: [/docs/examples/configuration/config-example.yaml](/docs/examples/configuration/config-example.yaml)\n\nFor example, you can use this command to create the default configuration file,\nif not already present:\n\n```sh\nnavi info config-example > \"$(navi info config-path)\"\n```\n\n## Default cheatsheets path\n\nNavi exposes its default cheatsheets path with:\n\n```sh\nnavi info cheats-path\n```\n\n> [!NOTE]\n> See [/docs/configuration/](/docs/configuration/README.md#the-default-cheatsheets-path) for more details on how the cheatsheets path is defined.\n\n"
  },
  {
    "path": "docs/usage/commands/repo/README.md",
    "content": "# The repo subcommands of navi\n\n<!-- TOC -->\n* [The repo subcommands of navi](#the-repo-subcommands-of-navi)\n  * [Commands Reference](#commands-reference)\n  * [Browsing through cheatsheet repositories](#browsing-through-cheatsheet-repositories)\n  * [Importing cheatsheet repositories](#importing-cheatsheet-repositories)\n<!-- TOC -->\n\n## Commands Reference\n\n| Command | Description                                                       |\n|---------|-------------------------------------------------------------------|\n| add     | Lets you import a cheatsheet repository                           |\n| browser | Lets you browse through a curated list of cheatsheet repositories |\n\n## Browsing through cheatsheet repositories\n\nNavi lets you browse featured [GitHub](https://github.com) repositories registered in [@denisidoro/cheats/featured_repos.txt](https://github.com/denisidoro/cheats/blob/master/featured_repos.txt).\n\nYou can find them within navi with the following command:\n\n```sh\nnavi repo browse\n```\n\n## Importing cheatsheet repositories\n\nYou can import `cheatsheet repositories` using a working git-clone format.\\\nThis includes using an HTTPS URL or an SSH URI.\n\n- Import using HTTPS\n\n    ```sh\n    navi repo add https://github.com/denisidoro/cheats\n    ```\n\n- Import using SSH\n\n    ```shell\n    navi repo add git@github.com:denisidoro/cheats\n    ```\n\n> [!CAUTION]\n> Despite `$NAVI_PATH` being set, it will not be used when installing cheat sheets directly via navi's own commands.\\\n> For example when running `navi add repo <repo>`, the default paths will still be used.\n> \n> To avoid this, you may simply clone repos via a regular `git clone` command, directly into `$NAVI_PATH`.\n"
  },
  {
    "path": "docs/usage/fzf-overrides/README.md",
    "content": "# The FZF Overrides of Navi\n\nNavi allows you to override certain parts of FZF in multiple ways.\n\n<!-- TOC -->\n* [The FZF Overrides of Navi](#the-fzf-overrides-of-navi)\n  * [Command line arguments](#command-line-arguments)\n  * [Environment variables](#environment-variables)\n<!-- TOC -->\n\n## Command line arguments\n\nNavi allows you to use command line arguments in order to override fzf values:\n\n```sh\n# if you want to override only when selecting snippets\nnavi --fzf-overrides '--height 3'\n\n# if you want to override only when selecting argument values\nnavi --fzf-overrides-var '--height 3'\n```\n\n## Environment variables\n\nNavi allows you to use environment variables in order to override fzf values.\n\n```bash\n# if you want to override for all cases\nFZF_DEFAULT_OPTS=\"--height 3\" navi\n```\n"
  },
  {
    "path": "docs/usage/shell-scripting/README.md",
    "content": "# Navi and shell scripting\n\nYou can use Navi with shell scripting.\n\n<!-- TOC -->\n* [Navi and shell scripting](#navi-and-shell-scripting)\n  * [Simply calling a cheat](#simply-calling-a-cheat)\n  * [Defining variables while calling](#defining-variables-while-calling)\n  * [Filtering results for a variable](#filtering-results-for-a-variable)\n  * [Selecting the best match for a variable](#selecting-the-best-match-for-a-variable)\n<!-- TOC -->\n\n> [NOTE!]\n> The following blog post gives you an example of a real world scenario: [denisidoro.github.io/posts/cli-templates/](https://denisidoro.github.io/posts/cli-templates/)\n\n\n## Simply calling a cheat\n\nBelow is an example on how to call a cheat from within navi:\n\n```sh\nnavi --query \"change branch\" --best-match\n```\n\n> [!NOTE]\n> Navi will ask the user to fill all arguments/variables needed.\n\n## Defining variables while calling\n\nIf you want to set the `<branch>` beforehand in your script, you can do as follows:\n\n```sh\nbranch=\"master\" navi --query \"change branch\" --best-match\n```\n\nNavi will not show any interactive input and `<branch>` will be exactly the one defined while calling.\n\n## Filtering results for a variable\n\nIf you want to filter some results for `<branch>`, you can do as follows:\n\n```sh\nbranch__query=\"master\" navi --query \"change branch\" --best-match\n```\n\nNavi will show any interactive input, unless a single entry is automatically selected and\nthe value for `<branch>` will be the one selected by the user.\n\n## Selecting the best match for a variable\n\nIf you want to select the best match for `<branch>`, you can do as follows:\n\n```sh\nbranch__best=\"master\" navi --query \"change branch\" --best-match\n```\n\nNavi will not show any interactive input, and the value for `<branch>` will be the one that\nbest matches the value passed as argument.\n"
  },
  {
    "path": "docs/widgets/README.md",
    "content": "# Navi widgets\n\nYou want to launch Navi with a shortcut?\\\nWidgets are here for you!\n\nWidgets are 3rd-party contributions and integrates Navi with 3rd-party software such as shells.\n\n## List of shell widgets\n\n| Shell      | Navi support       |\n|------------|--------------------|\n| Bash       | :white_check_mark: |\n| Fish       |                    |\n| Zsh        |                    |\n| NuShell    | :white_check_mark: |\n| PowerShell | :white_check_mark: |\n\n## PowerShell Widget\n\n- Removal\n\n```powershell\nRemove-Module navi.plugin\n```\n\n## Other widgets\n\n- Tmux\n- Vim\n\n\n### Installing the shell widget\n\nIf you want to install it, add this line to your `.bashrc`-like file:\n\n```sh\n# bash\neval \"$(navi widget bash)\"\n\n# zsh\neval \"$(navi widget zsh)\"\n\n# fish\nnavi widget fish | source\n\n# elvish\neval (navi widget elvish | slurp)\n\n# xonsh\n# xpip install xontrib-navi # ← run in your xonsh session to install xontrib\nxontrib load navi # ← add to your xonsh run control file\n```\n\n#### Nushell\n\nDue to Nushell's [unique design](https://www.nushell.sh/book/thinking_in_nu.html#think-of-nushell-as-a-compiled-language), it is not possible to `eval` a piece of code dynamically like in other shells therefore the integration process is a bit more involved. Here is an example:\n1. run `^navi widget nushell | save ($nu.default-config-dir | path join \"navi-integration.nu\")`\n2. add the following lines to `config.nu`:\n    ```nushell\n    source ($nu.default-config-dir | path join \"navi-integration.nu\")\n    ```\n\n\nBy default, `Ctrl+G` is assigned to launching **navi** (in xonsh can be customized with `$X_NAVI_KEY`, see [xontrib-navi](https://github.com/eugenesvk/xontrib-navi) for details).\n\nThere's currently no way to customize the widget behavior out-of-the-box. If you want to change the keybinding or the **navi** flags used by the widget, please:\n\n1. run, e.g., `navi widget bash` in your terminal\n2. copy the output\n3. paste the output in your `.bashrc`-like file\n4. edit the contents accordingly\n"
  },
  {
    "path": "docs/widgets/howto/TMUX.md",
    "content": "# Tmux widget\r\n\r\nYou can use **navi** as a [Tmux](https://github.com/tmux/tmux/wiki) widget to reach your Vim commands,\r\noften used SQL queries, etc. in any command-line app even in SSH sessions.\r\n\r\n<!-- TOC -->\r\n* [Tmux widget](#tmux-widget)\r\n  * [Keybinding navi](#keybinding-navi)\r\n  * [Example cheatsheet](#example-cheatsheet)\r\n<!-- TOC -->\r\n\r\n## Keybinding navi\r\n\r\nTo be able to open navi via <kbd> prefix + C-g </kbd>, you need to add the following lines\r\nto your Tmux configuration file.\r\n\r\n```sh\r\nbind-key -N \"Open Navi (cheat sheets)\" -T prefix C-g split-window \\\r\n  \"$SHELL --login -i -c 'navi --print | tmux load-buffer -b tmp - ; tmux paste-buffer -p -t {last} -b tmp -d'\"\r\n```\r\n\r\n## Example cheatsheet\r\n\r\nHere is an example cheatsheet to use inside Tmux:\r\n\r\n```sh\r\n% vim \r\n\r\n# Quit without save\r\nqa!\r\n\r\n# Delete a paragraph\r\nnormal dap\r\n\r\n# Generate sequence of numbers\r\nput =range(<start>, <stop>)\r\n\r\n% postgresql\r\n\r\n# Describe table columns in `psql` or `pgcli`\r\nselect \r\n   table_name, \r\n   column_name, \r\n   data_type \r\nfrom \r\n   information_schema.columns\r\nwhere \r\n   table_name = '<table>';\r\n```\r\n"
  },
  {
    "path": "docs/widgets/howto/VIM.md",
    "content": "# Vim widget\n\n<!-- TOC -->\n* [Vim widget](#vim-widget)\n  * [Syntax Highlighting](#syntax-highlighting)\n<!-- TOC -->\n\n## Syntax Highlighting\n\nIf you want syntax highlighting support for Navi in Vim, you need to\nadd those syntax rules to your syntax files such as at `$VIMRUNTIME/syntax/navi.vim`.\n\nThe rules are defined based on the [Cheatsheet syntax](/docs/cheatsheet/syntax/README.md).\n\nHere is an example:\n\n```vim\nsyntax match Comment \"\\v^;.*$\"\nsyntax match Statement \"\\v^\\%.*$\"\nsyntax match Operator \"\\v^\\#.*$\"\nsyntax match String \"\\v\\<.{-}\\>\"\nsyntax match String \"\\v^\\$.*$\"\n```\n"
  },
  {
    "path": "rust-toolchain.toml",
    "content": "[toolchain]\nchannel = \"1.81.0\"\ncomponents = [ \"rustfmt\", \"clippy\" ]\n"
  },
  {
    "path": "rustfmt.toml",
    "content": "max_width = 110\n"
  },
  {
    "path": "scripts/docker",
    "content": "#!/usr/bin/env bash\nset -euo pipefail\n\nexport NAVI_HOME=\"$(cd \"$(dirname \"$0\")/..\" && pwd)\"\n\n_start() {\n   cd \"$NAVI_HOME\"\n\n   ./scripts/release x86_64-unknown-linux-musl\n\n   docker run \\\n      -e HOMEBREW_NO_AUTO_UPDATE=1 \\\n      -e HOMEBREW_NO_INSTALL_CLEANUP=1 \\\n      -v \"$(pwd):/navi\" \\\n      -it 'bashell/alpine-bash' \\\n      bash -c '/navi/scripts docker setup; exec bash'\n}\n\n_setup() {\n   apk add git\n   apk add curl\n   git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf\n   ln -s /navi/target/debug/navi /usr/local/bin/navi\n}\n\nmain() {\n   local -r fn=\"$1\"\n   shift || true\n   \"_${fn}\" \"$@\"\n}\n\nmain \"$@\""
  },
  {
    "path": "scripts/dot",
    "content": "#!/usr/bin/env bash\nset -euo pipefail\n\nexport NAVI_HOME=\"$(cd \"$(dirname \"$0\")/..\" && pwd)\"\nexport PROJ_HOME=\"$NAVI_HOME\"\nexport PROJ_NAME=\"navi\"\nexport CARGO_PATH=\"${NAVI_HOME}/core/Cargo.toml\"\n\n# TODO: bump dotfiles + remove this fn\nlog::note() { log::info \"$@\"; }\n\ncargo() {\n   if [ \"${1:-}\" = \"install\" ] && [ \"${2:-}\" = \"cross\" ]; then\n      shift 2 || true\n      command cargo install cross --git https://github.com/cross-rs/cross \"$@\"\n   else\n      command cargo \"$@\"\n   fi\n}\n\nexport -f log::note cargo\n\ndot::clone() {\n  git clone 'https://github.com/denisidoro/dotfiles' \"$DOTFILES\"\n  cd \"$DOTFILES\"\n  git checkout 'v2022.07.16'\n}\n\ndot::clone_if_necessary() {\n  [ -n \"${DOTFILES:-}\" ] && [ -x \"${DOTFILES}/bin/dot\" ] && return\n  export DOTFILES=\"${NAVI_HOME}/target/dotfiles\"\n  dot::clone\n}\n\ndot::clone_if_necessary\n\n\"${DOTFILES}/bin/dot\" \"$@\"\n"
  },
  {
    "path": "scripts/install",
    "content": "#!/usr/bin/env bash\nset -euo pipefail\n\nif ${X_MODE:-false}; then\n   set -x\nfi\n\n# =====================\n# paths\n# =====================\n\nexport CARGO_DEFAULT_BIN=\"${HOME}/.cargo/bin\"\nexport BIN_DIR=\"${BIN_DIR:-\"$CARGO_DEFAULT_BIN\"}\"\n\n\n# =====================\n# logging\n# =====================\n\nechoerr() {\n   echo \"$@\" 1>&2\n}\n\ntap() {\n   local -r x=\"$(cat)\"\n   echoerr \"$x\"\n   echo \"$x\"\n}\n\nlog::ansi() {\n   local bg=false\n   case \"$@\" in\n      *reset*) echo \"\\e[0m\"; return 0 ;;\n      *black*) color=30 ;;\n      *red*) color=31 ;;\n      *green*) color=32 ;;\n      *yellow*) color=33 ;;\n      *blue*) color=34 ;;\n      *purple*) color=35 ;;\n      *cyan*) color=36 ;;\n      *white*) color=37 ;;\n   esac\n   case \"$@\" in\n      *regular*) mod=0 ;;\n      *bold*) mod=1 ;;\n      *underline*) mod=4 ;;\n   esac\n   case \"$@\" in\n      *background*) bg=true ;;\n      *bg*) bg=true ;;\n   esac\n\n   if $bg; then\n      echo \"\\e[${color}m\"\n   else\n      echo \"\\e[${mod:-0};${color}m\"\n   fi\n}\n\n_log() {\n   local template=\"$1\"\n   shift\n   echoerr \"$(printf \"$template\" \"$@\")\"\n}\n\n_header() {\n   local TOTAL_CHARS=60\n   local total=$TOTAL_CHARS-2\n   local size=${#1}\n   local left=$((($total - $size) / 2))\n   local right=$(($total - $size - $left))\n   printf \"%${left}s\" '' | tr ' ' =\n   printf \" $1 \"\n   printf \"%${right}s\" '' | tr ' ' =\n}\n\nlog::header() { _log \"\\n$(log::ansi bold)$(log::ansi purple)$(_header \"$1\")$(log::ansi reset)\\n\"; }\nlog::success() { _log \"$(log::ansi green)✔ %s$(log::ansi reset)\\n\" \"$@\"; }\nlog::error() { _log \"$(log::ansi red)✖ %s$(log::ansi reset)\\n\" \"$@\"; }\nlog::warning() { _log \"$(log::ansi yellow)➜ %s$(log::ansi reset)\\n\" \"$@\"; }\nlog::note() { _log \"$(log::ansi blue)%s$(log::ansi reset)\\n\" \"$@\"; }\n\n# TODO: remove\nheader() {\n   echoerr \"$*\"\n   echoerr\n}\n\ndie() {\n   log::error \"$@\"\n   exit 42\n}\n\nno_binary_warning() {\n   log::note \"There's no precompiled binary for your platform: $(uname -a)\"\n}\n\ninstallation_finish_instructions() {\n   local -r shell=\"$(get_shell)\"\n   log::note -e \"Finished. To call navi, restart your shell or reload the config file:\\n   source ~/.${shell}rc\"\n   local code\n   if [[ \"$shell\" == \"zsh\" ]]; then\n      code=\"navi widget ${shell} | source\"\n   else\n      code='source <(navi widget '\"$shell\"')'\n   fi\n   log::note -e \"\\nTo add the Ctrl-G keybinding, add the following to ~/.${shell}rc:\\n   ${code}\"\n}\n\n\n# =====================\n# security\n# =====================\n\nsha256() {\n   if command_exists sha256sum; then\n      sha256sum\n   elif command_exists shasum; then\n      shasum -a 256\n   elif command_exists openssl; then\n      openssl dgst -sha256\n   else\n      log::note \"Unable to calculate sha256!\"\n      exit 43\n   fi\n}\n\n\n# =====================\n# github\n# =====================\n\nlatest_version_released() {\n   curl -s 'https://api.github.com/repos/denisidoro/navi/releases/latest' \\\n      | grep -Eo '\"html_url\": \"https://github.com/denisidoro/navi/releases/tag/v([0-9\\.]+)' \\\n      | sed 's|\"html_url\": \"https://github.com/denisidoro/navi/releases/tag/v||'\n}\n\nasset_url() {\n   local -r version=\"$1\"\n   local -r variant=\"${2:-}\"\n\n   if [[ -n \"$variant\" ]]; then\n      echo \"https://github.com/denisidoro/navi/releases/download/v${version}/navi-v${version}-${variant}.tar.gz\"\n   else\n      echo \"https://github.com/denisidoro/navi/archive/v${version}.tar.gz\"\n   fi\n}\n\ndownload_asset() {\n   local -r tmp_dir=\"$(mktemp -d -t navi-install-XXXX)\"\n   local -r url=\"$(asset_url \"$@\")\"\n   log::note \"Downloading ${url}...\"\n   cd \"$tmp_dir\"\n   curl -L \"$url\" -o navi.tar.gz\n   tar xvzf navi.tar.gz\n   mkdir -p \"${BIN_DIR}\" &>/dev/null || true\n   mv \"./navi\" \"${BIN_DIR}/navi\"\n}\n\nsha_for_asset_on_github() {\n   local -r url=\"$(asset_url \"$@\")\"\n   curl -sL \"$url\" | sha256 | awk '{print $1}'\n}\n\nerror_installing() {\n   log::error \"Unable to install navi. Please check https://github.com/denisidoro/navi for alternative installation instructions\"\n   exit 33\n}\n\n\n# =====================\n# code\n# =====================\n\nversion_from_toml() {\n   cat \"${NAVI_HOME}/Cargo.toml\" \\\n      | grep version \\\n      | head -n1 \\\n      | awk '{print $NF}' \\\n      | tr -d '\"' \\\n      | tr -d \"'\"\n}\n\n\n# =====================\n# platform\n# =====================\n\ncommand_exists() {\n   type \"$1\" &>/dev/null\n}\n\nget_target() {\n   local -r unamea=\"$(uname -a)\"\n   local -r archi=\"$(uname -sm)\"\n\n   local target\n   case \"$unamea $archi\" in\n      *arwin*) target=\"x86_64-apple-darwin\" ;;\n      *inux*x86*) target=\"x86_64-unknown-linux-musl\" ;;\n      *ndroid*aarch*|*ndroid*arm*) target=\"aarch64-linux-android\" ;;\n      *inux*aarch*|*inux*arm*) target=\"armv7-unknown-linux-musleabihf\" ;;\n      *) target=\"\" ;;\n   esac\n\n   echo \"$target\"\n}\n\nget_shell() {\n   echo $SHELL | xargs basename\n}\n\n\n# =====================\n# main\n# =====================\n\nexport_path_cmd() {\n   echo\n   echo '  export PATH=\"${PATH}:'\"$1\"'\"'\n}\n\nappend_to_file() {\n   local -r path=\"$1\"\n   local -r text=\"$2\"\n   if [ -f \"$path\" ]; then\n      echo \"$text\" >> \"$path\"\n   fi\n}\n\nget_navi_bin_path() {\n   local file=\"${BIN_DIR}/navi\"\n   if [ -f \"$file\" ]; then\n      echo \"$file\"\n      return 0\n   fi\n   file=\"${CARGO_DEFAULT_BIN}/navi\"\n   if [ -f \"$file\" ]; then\n      echo \"$file\"\n      return 0\n   fi\n}\n\ninstall_navi() {\n   local -r target=\"$(get_target)\"\n\n   if command_exists navi; then\n      log::success \"navi is already installed\"\n      exit 0\n\n   elif command_exists brew; then\n      brew install navi\n   \n   elif [[ -n \"$target\" ]]; then\n      local -r version=\"$(latest_version_released)\"\n      download_asset \"$version\" \"$target\" || error_installing\n\n   elif command_exists cargo; then\n      cargo install navi\n\n   else\n      error_installing\n\n   fi\n\n   hash -r 2>/dev/null || true\n\n   local navi_bin_path=\"$(which navi || get_navi_bin_path)\"\n   ln -s \"$navi_bin_path\" \"${BIN_DIR}/navi\" &>/dev/null || true\n   if [ -f \"${BIN_DIR}/navi\" ]; then\n      navi_bin_path=\"${BIN_DIR}/navi\"\n   fi\n\n   local -r navi_bin_dir=\"$(dirname \"$navi_bin_path\")\"\n\n   echoerr\n   log::success \"Finished\"\n   log::success \"navi is now available at ${navi_bin_path}\"\n   echoerr\n\n   if echo \"$PATH\" | grep -q \"$navi_bin_dir\"; then\n      :\n   else\n      local -r cmd=\"$(export_path_cmd \"$navi_bin_dir\")\"\n      append_to_file \"${HOME}/.bashrc\" \"$cmd\"\n      append_to_file \"${ZDOTDIR:-\"$HOME\"}/.zshrc\" \"$cmd\"\n      append_to_file \"${HOME}/.fishrc\" \"$cmd\"\n   fi\n\n   log::note \"To call navi, restart your shell or reload your .bashrc-like config file\"\n   echo\n   log::note \"Check https://github.com/denisidoro/navi for more info\"\n\n   export PATH=\"${PATH}:${navi_bin_dir}\"\n\n   return 0\n}\n\n(return 0 2>/dev/null) || install_navi \"$@\"\n"
  },
  {
    "path": "scripts/make",
    "content": "#!/usr/bin/env bash\nset -euo pipefail\n\n##? make install\n##? make uninstall\n\nexport NAVI_HOME=\"$(cd \"$(dirname \"$0\")/..\" && pwd)\"\nsource \"${NAVI_HOME}/scripts/install\"\n\ninstall() {\n   cargo install --path .\n}\n\nuninstall() {\n   cargo uninstall\n}\n\nfix() {\n   \"${NAVI_HOME}/scripts/fix\"\n}\n\ncmd=\"$1\"\nshift\n\nexport X_MODE=true\nset -x\n\ncase \"$cmd\" in\n   \"install\") install \"$@\" ;;\n   \"uninstall\") uninstall \"$@\" ;;\n   \"fix\") fix \"$@\" ;;\nesac\n"
  },
  {
    "path": "scripts/release",
    "content": "#!/usr/bin/env bash\nset -euo pipefail\n\n### --------------------------------------------------------------------------------------------------------------------\n###     Logging functions\n### --------------------------------------------------------------------------------------------------------------------\n\nlog::info() {\n  ### Will print `[INFO]` in black foreground colour and magenta background colour\n  ### then will print the given text in a magenta foreground colour and default background colour.\n  printf \"\\033[35m\\033[7m[INFO]\\033[27;39m \\033[35m$*\\033[39m\\n\"\n}\n\nlog::error() {\n  ### Will print `[ERROR]` in black foreground colour and red background colour\n  ### then will print the given text in a red foreground colour and default background colour.\n  printf \"\\033[31m\\033[7m[ERROR]\\033[27;39m \\033[31m$*\\033[39m\\n\"\n}\n\nlog::warn() {\n  ### Will print `[WARNING]` in black foreground colour and yellow background colour\n  ### then will print the given text in a yellow foreground colour and default background colour.\n  printf \"\\033[33m\\033[7m[WARNING]\\033[27;39m \\033[33m$*\\033[39m\\n\"\n}\n\n### --------------------------------------------------------------------------------------------------------------------\n###     Utils functions\n### --------------------------------------------------------------------------------------------------------------------\n\n### Permits us to know if the current target environment\n### is a windows platform or not.\nis_windows() {\n   local -r target=\"$1\"\n   echo \"$target\" | grep -q \"windows\"\n}\n\n### NOTE: This function is currently not in use but kept as\n###       a backup function in case something breaks\n###\n### Returns the target environment, with a fix for the x86_64 target.\nget_env_target() {\n   eval \"$(rustc --print cfg | grep target)\"\n   local -rr raw=\"${target_arch:-}-${target_vendor:-}-${target_os:-}-${target_env:-}\"\n\n   if echo \"$raw\" | grep -q \"x86_64-apple-macos\"; then\n      echo \"x86_64-apple-darwin\"\n   else\n      echo \"$raw\"\n   fi\n}\n\n### NOTE: This function is currently not in use but kept as\n###       a backup function in case something breaks\n###\n### Logs the given arguments then execute it\n_tap() {\n   log::info \"$@\"\n   \"$@\"\n}\n\n### NOTE: This function is currently not in use but kept as\n###       a backup function in case something breaks\n###\n### Lists the content of a path, given as parameter.\n_ls() {\n   log::info \"contents from $*:\"\n   ls -la \"$@\" || true\n}\n\n### --------------------------------------------------------------------------------------------------------------------\n###     Release-Related functions\n### --------------------------------------------------------------------------------------------------------------------\n\nrelease() {\n  local -r env_target=\"$1\"\n  log::info \"env target: $env_target\"\n\n  local -r cross_target=\"${1:-\"$env_target\"}\"\n  log::info \"desired target: $cross_target\"\n\n  TAR_DIR=\"$(pwd)/target/tar\"\n\n  ### We clean up the target folder, just in case\n  rm -rf \"$(pwd)/target\" 2> /dev/null || true\n\n  ### We add the target for rustup in case cross doesn't find it.\n  ### Since the default behaviour of cross is to compile from\n  ### a rustup target if it doesn't find one for itself.\n  rustup target add $env_target\n  cargo install cross --locked\n\n  ### We're building the release via cross for the target environment\n  cross build --release --target \"$env_target\"\n\n  cd target/\"$env_target\"/release/\n\n  if is_windows \"$env_target\"; then\n    ### If our target is windows, we can simply zip our executable\n    ### since having tar is not the norm and neither the default\n    zip -r \"navi.zip\" \"navi.exe\"\n\n    ### We export a CI/CD variable to be used later in the pipeline\n    echo \"EXTENSION=zip\" >> $GITHUB_OUTPUT\n  else\n\n    ### @alexis-opolka - I'm currently disabling the usage of UPX since I cannot find how\n    ###     it was used before the merge of the code from the @denisidoro/dotfiles repository.\n    ###\n    #if upx --best --lzma \"navi\"; then\n    #  log::info \"upx succeeded\"\n    #else\n    #  log::info \"upx failed\"\n    #fi\n\n\n    ### For all other targets, they have tar as the norm\n    ### or have it installed by default.\n    tar -czf \"navi.tar.gz\" \"navi\"\n\n    ### We export a CI/CD variable to be used later in the pipeline\n    echo \"EXTENSION=tar.gz\" >> $GITHUB_OUTPUT\n  fi\n}\n\n### --------------------------------------------------------------------------------------------------------------------\n###     Main script\n### --------------------------------------------------------------------------------------------------------------------\n\nrelease \"$@\"\n"
  },
  {
    "path": "scripts/test",
    "content": "#!/usr/bin/env bash\nset -euo pipefail\n\nexport NAVI_HOME=\"$(cd \"$(dirname \"$0\")/..\" && pwd)\"\nsource \"${NAVI_HOME}/scripts/install\"\n\n\"${NAVI_HOME}/tests/run\""
  },
  {
    "path": "shell/navi.plugin.bash",
    "content": "#!/usr/bin/env bash\n\n_navi_call() {\n   local result=\"$(navi \"$@\" </dev/tty)\"\n   printf \"%s\" \"$result\"\n}\n\n_navi_widget() {\n   local -r input=\"${READLINE_LINE}\"\n   local -r last_command=\"$(echo \"${input}\" | navi fn widget::last_command)\"\n\n   if [ -z \"${last_command}\" ]; then\n      local -r output=\"$(_navi_call --print)\"\n   else\n      local -r find=\"${last_command}_NAVIEND\"\n      local -r replacement=\"$(_navi_call --print --query \"$last_command\")\"\n      local output=\"$input\"\n      if [ -n \"$replacement\" ]; then\n        output=\"${input}_NAVIEND\"\n        output=$(\n          shopt -u patsub_replacement\n          printf '%s' \"${output//\"$find\"/\"$replacement\"}\"\n        )\n      fi\n   fi\n\n   READLINE_LINE=\"$output\"\n   READLINE_POINT=${#READLINE_LINE}\n}\n\n_navi_widget_legacy() {\n   _navi_call --print\n}\n\nif [ ${BASH_VERSION:0:1} -lt 4 ]; then\n   bind '\"\\C-g\": \" \\C-b\\C-k \\C-u`_navi_widget_legacy`\\e\\C-e\\C-a\\C-y\\C-h\\C-e\\e \\C-y\\ey\\C-x\\C-x\\C-f\"'\nelse\n   bind -x '\"\\C-g\": _navi_widget'\nfi\n"
  },
  {
    "path": "shell/navi.plugin.elv",
    "content": "use str\n\nfn call-navi {\n  if (eq $edit:current-command '') {\n    var answer = (navi --print)\n    edit:replace-input $answer\n  } elif (not (str:contains-any $edit:current-command '|')) {\n    var answer = (navi --print --query $edit:current-command)\n    if (not-eq $answer '') {\n      edit:replace-input $answer\n    }\n  } else {\n    var @cmds query = (str:split '|' $edit:current-command)\n    var answer = (\n      if (eq $query '') {\n        navi --print\n      } else {\n        navi --print --query $query\n      }\n    )\n\n    if (not-eq $answer '') {\n      set cmds = [$@cmds $answer]\n      edit:replace-input (str:join '| ' $cmds)\n    }\n  }\n}\n\nset edit:insert:binding[Alt-h] = { call-navi >/dev/tty 2>&1 }\n"
  },
  {
    "path": "shell/navi.plugin.fish",
    "content": "function _navi_smart_replace\n    set --local query (commandline --current-process | string trim)\n    set --local version_parts \"\"\n    if test -n \"$version\"\n        set version_parts (string split '.' $version)\n    else\n        set version_parts (string split '.' (string match -r '\\d+\\.\\d+\\.\\d+' (fish --version)))\n    end\n\n    set --local force_repaint false\n    # https://github.com/fish-shell/fish-shell/blob/d663f553dffba460d6d0bcdf93df21bda9ec6f3f/doc_src/interactive.rst?plain=1#L440\n    #  > Bindings that change the mode are supposed to call the repaint-mode bind function\n    #\n    # Related issues\n    #  - https://github.com/fish-shell/fish-shell/issues/5033\n    #  - https://github.com/fish-shell/fish-shell/issues/5860\n    #  - https://github.com/fish-shell/fish-shell/blob/d663f553dffba460d6d0bcdf93df21bda9ec6f3f/src/screen.rs#L531\n    #\n    # Introduced with: https://github.com/denisidoro/navi/pull/982\n    if test $version_parts[1] -ge 4\n        set force_repaint true\n    end\n\n    if test -n \"$query\"\n        set --local best_match (navi --print --query \"$query\" --best-match)\n        if test -n \"$best_match\"\n            # --replace without --current-process: --current-process treats newlines as process\n            # boundaries and flattens multi-line snippets into a single line\n            commandline --replace -- \"$best_match\"\n            commandline --function end-of-line\n        end\n    end\n\n    if test -z \"$best_match\"\n        set --local candidate (navi --print --query \"$query\")\n        if test -n \"$candidate\"\n            commandline --replace -- \"$candidate\"\n            commandline --function end-of-line\n        end\n    end\n\n    # always repaint to restore the prompt after fzf clobbers the terminal\n    if test \"$force_repaint\" = true\n        commandline --function repaint\n    end\nend\n\nbind \\cg _navi_smart_replace\nbind --mode insert \\cg _navi_smart_replace\n"
  },
  {
    "path": "shell/navi.plugin.nu",
    "content": "export def navi_widget [] {\n    let current_input = (commandline)\n    let last_command = ($current_input | navi fn widget::last_command | str trim)\n\n    match ($last_command | is-empty) {\n        true => {^navi --print | complete | get \"stdout\"}\n        false => {\n            let find = $\"($last_command)_NAVIEND\"\n            let replacement = (^navi --print --query $'($last_command)' | complete | get \"stdout\")\n\n            match ($replacement | str trim | is-empty) {\n                false => {$\"($current_input)_NAVIEND\" | str replace $find $replacement}\n                true => $current_input\n            }\n        }\n    } \n    | str trim\n    | commandline edit --replace $in\n    \n    commandline set-cursor --end\n}\n\nlet nav_keybinding = {\n    name: \"navi\",\n    modifier: control,\n    keycode: char_g,\n    mode: [emacs, vi_normal, vi_insert],\n    event: {\n        send: executehostcommand,\n        cmd: navi_widget,\n    }\n}\n\n$env.config.keybindings = ($env.config.keybindings | append $nav_keybinding)\n"
  },
  {
    "path": "shell/navi.plugin.ps1",
    "content": "\n\n$null = New-Module {\n\n    function Invoke-Navi {\n        $startArgs = @{\n            FileName = \"navi\";\n            Arguments = $args;\n            RedirectStandardOutput = $true;\n            WorkingDirectory = $PWD;\n            UseShellExecute = $false;\n        }\n        $p = [System.Diagnostics.Process]@{StartInfo = $startArgs}\n\n        [void]$p.Start()\n        $result = $p.StandardOutput.ReadToEnd()\n        $p.WaitForExit()\n\n        $result\n    }\n\n\n    ### Initial code from @lurebat (https://github.com/lurebat/)\n    ### See #570 (https://github.com/denisidoro/navi/issues/570) for its original contribution\n    function Invoke-NaviWidget {\n        $ast = $tokens = $errors = $cursor = $null\n        [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref] $ast, [ref] $tokens, [ref] $errors, [ref] $cursor)\n\n        $line = $ast.ToString().Trim()\n        $output = $null\n\n        if ([String]::IsNullOrEmpty($line)) {\n            $output = (Invoke-Navi \"--print\" | Out-String).Trim()\n        }\n        else {\n            $best_match = (Invoke-Navi \"--print --best-match --query `\"$line`\"\" | Out-String).Trim()\n            if ([String]::IsNullOrEmpty($best_match)) {\n                $output = (Invoke-Navi \"--print --query `\"$line`\"\" | Out-String).Trim()\n            }\n            else {\n                $output = $best_match\n            }\n        }\n\n        [Microsoft.PowerShell.PSConsoleReadLine]::RevertLine()\n        [Microsoft.PowerShell.PSConsoleReadLine]::InvokePrompt()\n\n        ### Handling the case when the user escapes without selecting any entry\n        if (-Not([String]::IsNullOrEmpty($output))) {\n            [Microsoft.PowerShell.PSConsoleReadLine]::Insert([String]$output)\n        }\n    }\n\n    Set-PSReadlineKeyHandler -BriefDescription \"A keybinding to open Navi Widget\" -Chord Ctrl+g -ScriptBlock { Invoke-NaviWidget }\n    Export-ModuleMember -Function @()\n}\n"
  },
  {
    "path": "shell/navi.plugin.zsh",
    "content": "#!/usr/bin/env zsh\n\n_navi_call() {\n   local result=\"$(navi \"$@\" </dev/tty)\"\n   printf \"%s\" \"$result\"\n}\n\n_navi_widget() {\n   local -r input=\"${LBUFFER}\"\n   local -r last_command=\"$(echo \"${input}\" | navi fn widget::last_command)\"\n   local replacement=\"$last_command\"\n\n   if [ -z \"$last_command\" ]; then\n      replacement=\"$(_navi_call --print)\"\n   elif [ \"$LASTWIDGET\" = \"_navi_widget\" ] && [ \"$input\" = \"$previous_output\" ]; then\n      replacement=\"$(_navi_call --print --query \"$last_command\")\"\n   else\n      replacement=\"$(_navi_call --print --best-match --query \"$last_command\")\"\n   fi\n\n   if [ -n \"$replacement\" ]; then\n      local -r find=\"${last_command}_NAVIEND\"\n      previous_output=\"${input}_NAVIEND\"\n      previous_output=\"${previous_output//$find/$replacement}\"\n   else\n      previous_output=\"$input\"\n   fi\n\n   zle kill-whole-line\n   LBUFFER=\"${previous_output}\"\n   region_highlight=(\"P0 100 bold\")\n   zle redisplay\n}\n\nzle -N _navi_widget\nbindkey '^g' _navi_widget\n"
  },
  {
    "path": "src/bin/main.rs",
    "content": "extern crate navi;\n\nuse crate::navi::prelude::*;\nuse thiserror::Error;\n\n#[derive(Error, Debug)]\n#[error(\n    \"\\rHey, listen! navi encountered a problem.\nDo you think this is a bug? File an issue at https://github.com/denisidoro/navi.\"\n)]\npub struct FileAnIssue {\n    #[source]\n    source: anyhow::Error,\n}\n\nimpl FileAnIssue {\n    pub fn new<SourceError>(source: SourceError) -> Self\n    where\n        SourceError: Into<anyhow::Error>,\n    {\n        FileAnIssue {\n            source: source.into(),\n        }\n    }\n}\n\nfn main() -> anyhow::Result<()> {\n    if let Err(err) = init_logger() {\n        // may need redir stderr to a file to show this log initialization error\n        eprintln!(\"failed to initialize logging: {err:?}\");\n    }\n    navi::handle().map_err(|e| {\n        error!(\"{e:?}\");\n        FileAnIssue::new(e).into()\n    })\n}\n\nfn init_logger() -> anyhow::Result<()> {\n    const FILE_NAME: &str = \"navi.log\";\n    let mut file = navi::default_config_pathbuf()?;\n    file.set_file_name(FILE_NAME);\n\n    // If config path doesn't exist, navi won't log.\n    if file.parent().map(|p| !p.exists()).unwrap_or(true) {\n        return Ok(());\n    }\n\n    let writer = std::fs::File::create(&file).with_context(|| format!(\"{file:?} is not created\"))?;\n    tracing::subscriber::set_global_default(\n        tracing_subscriber::fmt()\n            .with_ansi(false)\n            .with_writer(writer)\n            .with_env_filter(tracing_subscriber::EnvFilter::from_default_env())\n            .finish(),\n    )?;\n    debug!(\"tracing initialized\");\n\n    Ok(())\n}\n"
  },
  {
    "path": "src/clients/cheatsh.rs",
    "content": "use crate::prelude::*;\nuse std::process::Command;\n\nfn map_line(line: &str) -> String {\n    line.trim().trim_end_matches(':').to_string()\n}\n\nfn as_lines(query: &str, markdown: &str) -> Vec<String> {\n    format!(\n        \"% {query}, cheat.sh\n{markdown}\"\n    )\n    .lines()\n    .map(map_line)\n    .collect()\n}\n\npub fn call(query: &str) -> Result<Vec<String>> {\n    let args = [\"-qO-\", &format!(\"cheat.sh/{query}\")];\n\n    let child = Command::new(\"wget\")\n        .args(args)\n        .stdin(Stdio::piped())\n        .stdout(Stdio::piped())\n        .spawn();\n\n    let child = match child {\n        Ok(x) => x,\n        Err(_) => {\n            let msg = \"navi was unable to call wget.\nMake sure wget is correctly installed.\";\n            return Err(anyhow!(msg));\n        }\n    };\n\n    let out = child.wait_with_output().context(\"Failed to wait for wget\")?;\n\n    if let Some(0) = out.status.code() {\n        let stdout = out.stdout;\n        let plain_bytes = strip_ansi_escapes::strip(stdout);\n\n        let markdown = String::from_utf8(plain_bytes).context(\"Output is invalid utf8\")?;\n        if markdown.starts_with(\"Unknown topic.\") {\n            let msg = format!(\n                \"`{}` not found in cheatsh.\nOutput:\n{}\n\",\n                &query, markdown,\n            );\n            return Err(anyhow!(msg));\n        }\n\n        let lines = as_lines(query, &markdown);\n        Ok(lines)\n    } else {\n        let msg = format!(\n            \"Failed to call:\nwget {}\n\nOutput:\n{}\n\nError:\n{}\n\",\n            args.join(\" \"),\n            String::from_utf8(out.stdout).unwrap_or_else(|_e| \"Unable to get output message\".to_string()),\n            String::from_utf8(out.stderr).unwrap_or_else(|_e| \"Unable to get error message\".to_string())\n        );\n        Err(anyhow!(msg))\n    }\n}\n"
  },
  {
    "path": "src/clients/mod.rs",
    "content": "pub mod cheatsh;\npub mod tldr;\n"
  },
  {
    "path": "src/clients/tldr.rs",
    "content": "use crate::config::CONFIG;\nuse crate::prelude::*;\nuse std::process::{Command, Stdio};\n\nlazy_static! {\n    pub static ref VAR_TLDR_REGEX: Regex = Regex::new(r\"\\{\\{(.*?)\\}\\}\").expect(\"Invalid regex\");\n    pub static ref NON_VAR_CHARS_REGEX: Regex = Regex::new(r\"[^\\da-zA-Z_]\").expect(\"Invalid regex\");\n}\n\nstatic VERSION_DISCLAIMER: &str =\n    \"tldr-c-client (the default one in Homebrew) doesn't support markdown files, so navi can't use it.\nThe recommended client is tealdeer(https://github.com/dbrgn/tealdeer).\";\n\nfn convert_tldr_vars(line: &str) -> String {\n    let caps = VAR_TLDR_REGEX.find_iter(line);\n    let mut new_line: String = line.to_string();\n    for cap in caps {\n        let braced_var = cap.as_str();\n        let var = &braced_var[2..braced_var.len() - 2];\n        let mut new_var = NON_VAR_CHARS_REGEX.replace_all(var, \"_\").to_string();\n        if let Some(c) = new_var.chars().next() {\n            if c.to_string().parse::<u8>().is_ok() {\n                new_var = format!(\"example_{new_var}\");\n            }\n        }\n        let bracketed_var = format!(\"<{new_var}>\");\n        new_line = new_line.replace(braced_var, &bracketed_var);\n    }\n    new_line\n}\n\nfn convert_tldr(line: &str) -> String {\n    let line = line.trim();\n    if line.starts_with('-') {\n        format!(\"{}{}\", \"# \", &line[2..line.len() - 1])\n    } else if line.starts_with('`') {\n        convert_tldr_vars(&line[1..line.len() - 1])\n    } else if line.starts_with('%') {\n        line.to_string()\n    } else {\n        \"\".to_string()\n    }\n}\n\nfn markdown_lines(query: &str, markdown: &str) -> Vec<String> {\n    format!(\n        \"% {query}, tldr\n {markdown}\"\n    )\n    .lines()\n    .map(convert_tldr)\n    .collect()\n}\n\npub fn call(query: &str) -> Result<Vec<String>> {\n    let tealdeer = CONFIG.tealdeer();\n    let output_flag = if tealdeer { \"--raw\" } else { \"--markdown\" };\n    let args = [query, output_flag];\n\n    let child = Command::new(\"tldr\")\n        .args(args)\n        .stdin(Stdio::piped())\n        .stdout(Stdio::piped())\n        .stderr(Stdio::piped())\n        .spawn();\n\n    let child = match child {\n        Ok(x) => x,\n        Err(_) => {\n            let msg = format!(\n                \"navi was unable to call tldr.\nMake sure tldr is correctly installed.\n\nNote:\n{VERSION_DISCLAIMER}\n\"\n            );\n            return Err(anyhow!(msg));\n        }\n    };\n\n    let out = child.wait_with_output().context(\"Failed to wait for tldr\")?;\n\n    if let Some(0) = out.status.code() {\n        let stdout = out.stdout;\n\n        let markdown = String::from_utf8(stdout).context(\"Output is invalid utf8\")?;\n        let lines = markdown_lines(query, &markdown);\n        Ok(lines)\n    } else {\n        let msg = format!(\n            \"Failed to call:\ntldr {}\n\nOutput:\n{}\n\nError:\n{}\n\nNote:\nThe client.tealdeer config option can be set to enable tealdeer support.\nIf you want to use another client, please make sure it supports the --markdown flag.\nIf you are already using a supported version you can ignore this message.\n{}\n\",\n            args.join(\" \"),\n            String::from_utf8(out.stdout).unwrap_or_else(|_e| \"Unable to get output message\".to_string()),\n            String::from_utf8(out.stderr).unwrap_or_else(|_e| \"Unable to get error message\".to_string()),\n            VERSION_DISCLAIMER,\n        );\n        Err(anyhow!(msg))\n    }\n}\n"
  },
  {
    "path": "src/commands/core/actor.rs",
    "content": "use crate::common::clipboard;\nuse crate::common::fs;\nuse crate::common::shell;\nuse crate::common::shell::ShellSpawnError;\nuse crate::config::Action;\nuse crate::deser;\nuse crate::env_var;\nuse crate::finder::structures::{Opts as FinderOpts, SuggestionType};\nuse crate::prelude::*;\nuse crate::structures::cheat::{Suggestion, VariableMap};\nuse crate::structures::item::Item;\nuse shell::EOF;\nuse std::process::Stdio;\n\nfn prompt_finder(\n    variable_name: &str,\n    suggestion: Option<&Suggestion>,\n    variable_count: usize,\n) -> Result<String> {\n    env_var::remove(env_var::PREVIEW_COLUMN);\n    env_var::remove(env_var::PREVIEW_DELIMITER);\n    env_var::remove(env_var::PREVIEW_MAP);\n\n    let mut extra_preview: Option<String> = None;\n\n    let (suggestions, initial_opts) = if let Some(s) = suggestion {\n        let (suggestion_command, suggestion_opts) = s;\n\n        if let Some(sopts) = suggestion_opts {\n            if let Some(c) = &sopts.column {\n                env_var::set(env_var::PREVIEW_COLUMN, c.to_string());\n            }\n            if let Some(d) = &sopts.delimiter {\n                env_var::set(env_var::PREVIEW_DELIMITER, d);\n            }\n            if let Some(m) = &sopts.map {\n                env_var::set(env_var::PREVIEW_MAP, m);\n            }\n            if let Some(p) = &sopts.preview {\n                extra_preview = Some(p.into());\n            }\n        }\n\n        let mut cmd = shell::out();\n        cmd.stdout(Stdio::piped()).arg(suggestion_command);\n        debug!(cmd = ?cmd);\n        let child = cmd\n            .spawn()\n            .map_err(|e| ShellSpawnError::new(suggestion_command, e))?;\n\n        let text = String::from_utf8(\n            child\n                .wait_with_output()\n                .context(\"Failed to wait and collect output from bash\")?\n                .stdout,\n        )\n        .context(\"Suggestions are invalid utf8\")?;\n\n        (text, suggestion_opts)\n    } else {\n        ('\\n'.to_string(), &None)\n    };\n\n    let exe = fs::exe_string();\n\n    let preview = if CONFIG.shell().contains(\"powershell\") || CONFIG.shell().contains(\"pwsh\") {\n        format!(\n            r#\"{exe} preview-var {{+}} \"{{q}}\" \"{name}\"; {extra}\"#,\n            exe = exe,\n            name = variable_name,\n            extra = extra_preview\n                .clone()\n                .map(|e| format!(\" echo; {e}\"))\n                .unwrap_or_default(),\n        )\n    } else if CONFIG.shell().contains(\"cmd.exe\") {\n        format!(\n            r#\"(@echo.{{+}}{eof}{{q}}{eof}{name}{eof}{extra}) | {exe} preview-var-stdin\"#,\n            exe = exe,\n            name = variable_name,\n            extra = extra_preview.clone().unwrap_or_default(),\n            eof = EOF,\n        )\n    } else if CONFIG.shell().contains(\"fish\") {\n        format!(\n            r#\"{exe} preview-var \"{{+}}\" \"{{q}}\" \"{name}\"; {extra}\"#,\n            exe = exe,\n            name = variable_name,\n            extra = extra_preview\n                .clone()\n                .map(|e| format!(\" echo; {e}\"))\n                .unwrap_or_default(),\n        )\n    } else {\n        format!(\n            r#\"{exe} preview-var \"$(cat <<{eof}\n{{+}}\n{eof}\n)\" \"$(cat <<{eof}\n{{q}}\n{eof}\n)\" \"{name}\"; {extra}\"#,\n            exe = exe,\n            name = variable_name,\n            extra = extra_preview\n                .clone()\n                .map(|e| format!(\" echo; {e}\"))\n                .unwrap_or_default(),\n            eof = EOF,\n        )\n    };\n\n    let mut opts = FinderOpts {\n        preview: Some(preview),\n        show_all_columns: true,\n        ..initial_opts.clone().unwrap_or_else(FinderOpts::var_default)\n    };\n\n    opts.query = env_var::get(format!(\"{variable_name}__query\")).ok();\n\n    if let Ok(f) = env_var::get(format!(\"{variable_name}__best\")) {\n        opts.filter = Some(f);\n        opts.suggestion_type = SuggestionType::SingleSelection;\n    }\n\n    if opts.preview_window.is_none() {\n        opts.preview_window = Some(if extra_preview.is_none() {\n            format!(\"up:{}\", variable_count + 3)\n        } else {\n            \"right:50%\".to_string()\n        });\n    }\n\n    if suggestion.is_none() {\n        opts.suggestion_type = SuggestionType::Disabled;\n    };\n\n    let (output, _) = CONFIG\n        .finder()\n        .call(opts, |stdin| {\n            stdin\n                .write_all(suggestions.as_bytes())\n                .context(\"Could not write to finder's stdin\")?;\n            Ok(())\n        })\n        .context(\"finder was unable to prompt with suggestions\")?;\n\n    Ok(output)\n}\n\nfn unique_result_count(results: &[&str]) -> usize {\n    let mut vars = results.to_owned();\n    vars.sort_unstable();\n    vars.dedup();\n    vars.len()\n}\n\nfn replace_variables_from_snippet(snippet: &str, tags: &str, variables: VariableMap) -> Result<String> {\n    let mut interpolated_snippet = String::from(snippet);\n\n    if CONFIG.prevent_interpolation() {\n        return Ok(interpolated_snippet);\n    }\n\n    let variables_found: Vec<&str> = deser::VAR_REGEX.find_iter(snippet).map(|m| m.as_str()).collect();\n    let variable_count = unique_result_count(&variables_found);\n\n    for bracketed_variable_name in variables_found {\n        let variable_name = &bracketed_variable_name[1..bracketed_variable_name.len() - 1];\n\n        let env_variable_name = env_var::escape(variable_name);\n        let env_value = env_var::get(&env_variable_name);\n\n        let value = if let Ok(e) = env_value {\n            e\n        } else if let Some(suggestion) = variables.get_suggestion(tags, variable_name) {\n            let mut new_suggestion = suggestion.clone();\n            new_suggestion.0 = replace_variables_from_snippet(&new_suggestion.0, tags, variables.clone())?;\n            prompt_finder(variable_name, Some(&new_suggestion), variable_count)?\n        } else {\n            prompt_finder(variable_name, None, variable_count)?\n        };\n\n        env_var::set(env_variable_name, &value);\n\n        interpolated_snippet = if value.as_str() == \"\\n\" {\n            interpolated_snippet.replacen(bracketed_variable_name, \"\", 1)\n        } else {\n            interpolated_snippet.replacen(bracketed_variable_name, value.as_str(), 1)\n        };\n    }\n\n    Ok(interpolated_snippet)\n}\n\npub fn with_absolute_path(snippet: String) -> String {\n    if let Some(s) = snippet.strip_prefix(\"navi \") {\n        return format!(\"{} {}\", fs::exe_string(), s);\n    }\n    snippet\n}\n\npub fn act(\n    extractions: Result<(&str, Item)>,\n    files: Vec<String>,\n    variables: Option<VariableMap>,\n) -> Result<()> {\n    let (\n        key,\n        Item {\n            tags,\n            comment,\n            snippet,\n            file_index,\n            ..\n        },\n    ) = extractions.unwrap();\n\n    if key == \"ctrl-o\" {\n        edit::edit_file(Path::new(&files[file_index.expect(\"No files found\")]))\n            .expect(\"Could not open file in external editor\");\n        return Ok(());\n    }\n\n    env_var::set(env_var::PREVIEW_INITIAL_SNIPPET, &snippet);\n    env_var::set(env_var::PREVIEW_TAGS, &tags);\n    env_var::set(env_var::PREVIEW_COMMENT, comment);\n\n    let interpolated_snippet = {\n        let mut s = replace_variables_from_snippet(\n            &snippet,\n            &tags,\n            variables.expect(\"No variables received from finder\"),\n        )\n        .context(\"Failed to replace variables from snippet\")?;\n        s = with_absolute_path(s);\n        s = deser::with_new_lines(s);\n        s\n    };\n\n    match CONFIG.action() {\n        Action::Print => {\n            println!(\"{interpolated_snippet}\");\n        }\n        Action::Execute => match key {\n            \"ctrl-y\" => {\n                clipboard::copy(interpolated_snippet)?;\n            }\n            _ => {\n                let mut cmd = shell::out();\n                cmd.arg(&interpolated_snippet[..]);\n                debug!(cmd = ?cmd);\n                cmd.spawn()\n                    .map_err(|e| ShellSpawnError::new(&interpolated_snippet[..], e))?\n                    .wait()\n                    .context(\"bash was not running\")?;\n            }\n        },\n    };\n\n    Ok(())\n}\n"
  },
  {
    "path": "src/commands/core/mod.rs",
    "content": "mod actor;\n\nuse crate::clients::{cheatsh, tldr};\nuse crate::config::Source;\nuse crate::deser;\nuse crate::filesystem;\nuse crate::finder::structures::Opts as FinderOpts;\nuse crate::parser::Parser;\nuse crate::prelude::*;\nuse crate::structures::fetcher::{Fetcher, StaticFetcher};\nuse crate::welcome;\n\npub fn init(fetcher: Box<dyn Fetcher>) -> Result<()> {\n    let config = &CONFIG;\n    let opts = FinderOpts::snippet_default();\n    debug!(\"opts = {opts:#?}\");\n    // let fetcher = config.fetcher();\n\n    let (raw_selection, (variables, files)) = config\n        .finder()\n        .call(opts, |writer| {\n            let mut parser = Parser::new(writer, true);\n\n            let found_something = fetcher\n                .fetch(&mut parser)\n                .context(\"Failed to parse variables intended for finder\")?;\n\n            if !found_something {\n                welcome::populate_cheatsheet(&mut parser)?;\n            }\n\n            Ok((Some(parser.variables), fetcher.files()))\n        })\n        .context(\"Failed getting selection and variables from finder\")?;\n\n    debug!(raw_selection = ?raw_selection);\n    let extractions = deser::terminal::read(&raw_selection, config.best_match());\n\n    if extractions.is_err() {\n        return init(fetcher);\n    }\n\n    actor::act(extractions, files, variables)?;\n\n    Ok(())\n}\n\npub fn get_fetcher() -> Result<Box<dyn Fetcher>> {\n    let source = CONFIG.source();\n    debug!(source = ?source);\n    match source {\n        Source::Cheats(query) => {\n            let lines = cheatsh::call(&query)?;\n            let fetcher = Box::new(StaticFetcher::new(lines));\n            Ok(fetcher)\n        }\n        Source::Tldr(query) => {\n            let lines = tldr::call(&query)?;\n            let fetcher = Box::new(StaticFetcher::new(lines));\n            Ok(fetcher)\n        }\n        Source::Filesystem(path) => {\n            let fetcher = Box::new(filesystem::Fetcher::new(path));\n            Ok(fetcher)\n        }\n        Source::Welcome => {\n            let fetcher = Box::new(welcome::Fetcher::new());\n            Ok(fetcher)\n        }\n    }\n}\n\npub fn main() -> Result<()> {\n    let fetcher = get_fetcher()?;\n    init(fetcher)\n}\n"
  },
  {
    "path": "src/commands/func/map.rs",
    "content": "use crate::common::shell::{self, ShellSpawnError};\nuse crate::prelude::*;\n\npub fn expand() -> Result<()> {\n    let cmd = r#\"sed -e 's/^.*$/\"&\"/' | tr '\\n' ' '\"#;\n    shell::out()\n        .arg(cmd)\n        .spawn()\n        .map_err(|e| ShellSpawnError::new(cmd, e))?\n        .wait()?;\n    Ok(())\n}\n"
  },
  {
    "path": "src/commands/func/mod.rs",
    "content": "mod map;\nmod widget;\n\nuse super::core;\nuse super::temp;\nuse crate::common::url;\nuse crate::prelude::*;\nuse clap::Args;\nuse clap::ValueEnum;\n\n#[derive(Debug, Clone, ValueEnum)]\npub enum Func {\n    #[value(name = \"url::open\")]\n    UrlOpen,\n    #[value(name = \"welcome\")]\n    Welcome,\n    #[value(name = \"widget::last_command\")]\n    WidgetLastCommand,\n    #[value(name = \"map::expand\")]\n    MapExpand,\n    #[value(name = \"temp\")]\n    Temp,\n}\n\n#[derive(Debug, Clone, Args)]\npub struct Input {\n    /// Function name (example: \"url::open\")\n    #[arg(ignore_case = true)]\n    pub func: Func,\n    /// List of arguments (example: \"https://google.com\")\n    pub args: Vec<String>,\n}\n\nimpl Runnable for Input {\n    fn run(&self) -> Result<()> {\n        let func = &self.func;\n        let args = self.args.clone(); // TODO\n\n        match func {\n            Func::UrlOpen => url::open(args),\n            Func::Welcome => core::main(),\n            Func::WidgetLastCommand => widget::last_command(),\n            Func::MapExpand => map::expand(),\n            Func::Temp => temp::main(),\n        }\n    }\n}\n"
  },
  {
    "path": "src/commands/func/widget.rs",
    "content": "use crate::prelude::*;\nuse std::io::{self, Read};\n\npub fn last_command() -> Result<()> {\n    let mut text = String::new();\n    io::stdin().read_to_string(&mut text)?;\n\n    let replacements = vec![(\"||\", \"ග\"), (\"|\", \"ඛ\"), (\"&&\", \"ඝ\")];\n\n    let parts = shellwords::split(&text).unwrap_or_else(|_| text.split('|').map(|s| s.to_string()).collect());\n\n    for p in parts {\n        for (pattern, escaped) in replacements.clone() {\n            if p.contains(pattern) && p != pattern && p != format!(\"{pattern}{pattern}\") {\n                let replacement = p.replace(pattern, escaped);\n                text = text.replace(&p, &replacement);\n            }\n        }\n    }\n\n    let mut extracted = text.clone();\n\n    for (pattern, _) in replacements.clone() {\n        let mut new_parts = text.rsplit(pattern);\n        if let Some(extracted_attempt) = new_parts.next() {\n            if extracted_attempt.len() <= extracted.len() {\n                extracted = extracted_attempt.to_string();\n            }\n        }\n    }\n\n    for (pattern, escaped) in replacements.clone() {\n        text = text.replace(escaped, pattern);\n        extracted = extracted.replace(escaped, pattern);\n    }\n\n    println!(\"{}\", extracted.trim_start());\n\n    Ok(())\n}\n"
  },
  {
    "path": "src/commands/info.rs",
    "content": "use crate::filesystem;\nuse crate::prelude::*;\nuse clap::{Args, Subcommand};\n\n#[derive(Debug, Clone, Args)]\npub struct Input {\n    #[clap(subcommand)]\n    pub info: Info,\n}\n\n#[derive(Debug, Clone, Subcommand)]\npub enum Info {\n    /// Prints a cheatsheet example.\n    CheatsExample,\n    /// Prints a configuration file example.\n    ConfigExample,\n\n    /// [DEPRECATED] Prints the default cheatsheets path.\n    /// Please use `info default-cheats-path` instead.\n    CheatsPath,\n    /// [DEPRECATED] Prints the default configuration path.\n    /// Please use `info default-config-path` instead.\n    ConfigPath,\n\n    /// Prints the default cheatsheets path.\n    DefaultCheatsPath,\n    /// Prints the default configuration path.\n    DefaultConfigPath,\n}\n\nimpl Runnable for Input {\n    fn run(&self) -> Result<()> {\n        let info = &self.info;\n\n        match info {\n            // Here should be the example commands\n            Info::CheatsExample => {\n                println!(\"{}\", include_str!(\"../../docs/examples/cheatsheet/example.cheat\"))\n            }\n            Info::ConfigExample => println!(\n                \"{}\",\n                include_str!(\"../../docs/examples/configuration/config-example.yaml\")\n            ),\n\n            // Here should be the old deprecated default value commands\n            Info::CheatsPath => println!(\"{}\", &filesystem::default_cheat_pathbuf()?.to_string()),\n            Info::ConfigPath => println!(\"{}\", &filesystem::default_config_pathbuf()?.to_string()),\n\n            // Here should be the default values (computed at compile time)\n            Info::DefaultCheatsPath => println!(\"{}\", &filesystem::default_cheat_pathbuf()?.to_string()),\n            Info::DefaultConfigPath => println!(\"{}\", &filesystem::default_config_pathbuf()?.to_string()),\n        }\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "src/commands/mod.rs",
    "content": "pub mod core;\npub mod func;\npub mod info;\npub mod preview;\npub mod repo;\npub mod shell;\npub mod temp;\n\nuse crate::commands;\nuse crate::prelude::*;\n\npub fn handle() -> Result<()> {\n    use crate::config::Command::*;\n\n    debug!(\"CONFIG = {:#?}\", &*CONFIG);\n    match CONFIG.cmd() {\n        None => commands::core::main(),\n\n        Some(c) => match c {\n            Preview(input) => input.run(),\n\n            PreviewVarStdin(input) => input.run(),\n\n            PreviewVar(input) => input.run(),\n\n            Widget(input) => input.run().context(\"Failed to print shell widget code\"),\n\n            Fn(input) => input\n                .run()\n                .with_context(|| format!(\"Failed to execute function `{:#?}`\", input.func)),\n\n            Info(input) => input\n                .run()\n                .with_context(|| format!(\"Failed to fetch info `{:#?}`\", input.info)),\n\n            #[cfg(not(feature = \"disable-repo-management\"))]\n            Repo(input) => input.run(),\n        },\n    }\n}\n"
  },
  {
    "path": "src/commands/preview/mod.rs",
    "content": "use crate::deser;\nuse crate::prelude::*;\nuse clap::Args;\nuse crossterm::style::{style, Stylize};\nuse std::process;\n\npub mod var;\npub mod var_stdin;\n\n#[derive(Debug, Clone, Args)]\npub struct Input {\n    /// Selection line\n    pub line: String,\n}\n\nfn extract_elements(argstr: &str) -> Result<(&str, &str, &str)> {\n    let mut parts = argstr.split(deser::terminal::DELIMITER).skip(3);\n    let tags = parts.next().context(\"No `tags` element provided.\")?;\n    let comment = parts.next().context(\"No `comment` element provided.\")?;\n    let snippet = parts.next().context(\"No `snippet` element provided.\")?;\n    Ok((tags, comment, snippet))\n}\n\nimpl Runnable for Input {\n    fn run(&self) -> Result<()> {\n        let line = &self.line;\n\n        let (tags, comment, snippet) = extract_elements(line)?;\n\n        println!(\n            \"{comment} {tags} \\n{snippet}\",\n            comment = style(comment).with(CONFIG.comment_color()),\n            tags = style(format!(\"[{tags}]\")).with(CONFIG.tag_color()),\n            snippet = style(deser::fix_newlines(snippet)).with(CONFIG.snippet_color()),\n        );\n\n        process::exit(0)\n    }\n}\n"
  },
  {
    "path": "src/commands/preview/var.rs",
    "content": "use crate::deser;\nuse crate::env_var;\nuse crate::finder;\nuse crate::prelude::*;\nuse clap::Args;\nuse crossterm::style::style;\nuse crossterm::style::Stylize;\nuse std::iter;\nuse std::process;\n\n#[derive(Debug, Clone, Args)]\npub struct Input {\n    /// Selection line\n    pub selection: String,\n    /// Query match\n    pub query: String,\n    /// Typed text\n    pub variable: String,\n}\n\nimpl Runnable for Input {\n    fn run(&self) -> Result<()> {\n        let selection = &self.selection;\n        let query = &self.query;\n        let variable = &self.variable;\n\n        let snippet = env_var::must_get(env_var::PREVIEW_INITIAL_SNIPPET);\n        let tags = env_var::must_get(env_var::PREVIEW_TAGS);\n        let comment = env_var::must_get(env_var::PREVIEW_COMMENT);\n        let column = env_var::parse(env_var::PREVIEW_COLUMN);\n        let delimiter = env_var::get(env_var::PREVIEW_DELIMITER).ok();\n        let map = env_var::get(env_var::PREVIEW_MAP).ok();\n\n        let active_color = CONFIG.tag_color();\n        let inactive_color = CONFIG.comment_color();\n\n        let mut colored_snippet = String::from(&snippet);\n        let mut visited_vars: HashSet<&str> = HashSet::new();\n\n        let mut variables = String::from(\"\");\n\n        println!(\n            \"{comment} {tags}\",\n            comment = style(comment).with(CONFIG.comment_color()),\n            tags = style(format!(\"[{tags}]\")).with(CONFIG.tag_color()),\n        );\n\n        let bracketed_current_variable = format!(\"<{variable}>\");\n\n        let bracketed_variables: Vec<&str> = {\n            if snippet.contains(&bracketed_current_variable) {\n                deser::VAR_REGEX.find_iter(&snippet).map(|m| m.as_str()).collect()\n            } else {\n                iter::once(&bracketed_current_variable)\n                    .map(|s| s.as_str())\n                    .collect()\n            }\n        };\n\n        for bracketed_variable_name in bracketed_variables {\n            let variable_name = &bracketed_variable_name[1..bracketed_variable_name.len() - 1];\n\n            if visited_vars.contains(variable_name) {\n                continue;\n            } else {\n                visited_vars.insert(variable_name);\n            }\n\n            let is_current = variable_name == variable;\n            let variable_color = if is_current { active_color } else { inactive_color };\n            let env_variable_name = env_var::escape(variable_name);\n\n            let value = if is_current {\n                let v = selection.trim_matches('\\'');\n                if v.is_empty() { query.trim_matches('\\'') } else { v }.to_string()\n            } else if let Ok(v) = env_var::get(&env_variable_name) {\n                v\n            } else {\n                \"\".to_string()\n            };\n\n            let replacement = format!(\n                \"{variable}\",\n                variable = style(bracketed_variable_name).with(variable_color),\n            );\n\n            colored_snippet = colored_snippet.replace(bracketed_variable_name, &replacement);\n\n            variables = format!(\n                \"{variables}\\n{variable} = {value}\",\n                variables = variables,\n                variable = style(variable_name).with(variable_color),\n                value = if env_var::get(&env_variable_name).is_ok() {\n                    value\n                } else if is_current {\n                    finder::process(value, column, delimiter.as_deref(), map.clone())\n                        .expect(\"Unable to process value\")\n                } else {\n                    \"\".to_string()\n                }\n            );\n        }\n\n        println!(\"{snippet}\", snippet = deser::fix_newlines(&colored_snippet));\n        println!(\"{variables}\");\n\n        process::exit(0)\n    }\n}\n"
  },
  {
    "path": "src/commands/preview/var_stdin.rs",
    "content": "use clap::Args;\n\nuse super::var;\nuse crate::common::shell::{self, ShellSpawnError, EOF};\nuse crate::prelude::*;\nuse std::io::{self, Read};\n\n#[derive(Debug, Clone, Args)]\npub struct Input {}\n\nimpl Runnable for Input {\n    fn run(&self) -> Result<()> {\n        let mut text = String::new();\n        io::stdin().read_to_string(&mut text)?;\n\n        let mut parts = text.split(EOF);\n        let selection = parts.next().expect(\"Unable to get selection\").to_owned();\n        let query = parts.next().expect(\"Unable to get query\").to_owned();\n        let variable = parts.next().expect(\"Unable to get variable\").trim().to_owned();\n\n        let input = var::Input {\n            selection,\n            query,\n            variable,\n        };\n\n        input.run()?;\n\n        if let Some(extra) = parts.next() {\n            if !extra.is_empty() {\n                print!(\"\");\n\n                let mut cmd = shell::out();\n                cmd.arg(extra);\n                debug!(?cmd);\n                cmd.spawn().map_err(|e| ShellSpawnError::new(extra, e))?.wait()?;\n            }\n        }\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "src/commands/repo/add.rs",
    "content": "use crate::common::git;\nuse crate::filesystem;\nuse crate::finder::structures::{Opts as FinderOpts, SuggestionType};\nuse crate::finder::FinderChoice;\nuse crate::prelude::*;\nuse std::fs;\nuse std::path;\n\nfn ask_if_should_import_all(finder: &FinderChoice) -> Result<bool> {\n    let opts = FinderOpts {\n        column: Some(1),\n        header: Some(\"Do you want to import all files from this repo?\".to_string()),\n        ..Default::default()\n    };\n\n    let (response, _) = finder\n        .call(opts, |stdin| {\n            stdin\n                .write_all(b\"Yes\\nNo\")\n                .context(\"Unable to writer alternatives\")?;\n            Ok(())\n        })\n        .context(\"Unable to get response\")?;\n\n    Ok(response.to_lowercase().starts_with('y'))\n}\n\npub fn main(uri: String) -> Result<()> {\n    let finder = CONFIG.finder();\n\n    let should_import_all = ask_if_should_import_all(&finder).unwrap_or(false);\n    let (actual_uri, user, repo) = git::meta(uri.as_str());\n\n    let cheat_pathbuf = filesystem::default_cheat_pathbuf()?;\n    let tmp_pathbuf = filesystem::tmp_pathbuf()?;\n    let tmp_path_str = &tmp_pathbuf.to_string();\n\n    let _ = filesystem::remove_dir(&tmp_pathbuf);\n    filesystem::create_dir(&tmp_pathbuf)?;\n\n    eprintln!(\"Cloning {} into {}...\\n\", &actual_uri, &tmp_path_str);\n\n    git::shallow_clone(actual_uri.as_str(), tmp_path_str)\n        .with_context(|| format!(\"Failed to clone `{actual_uri}`\"))?;\n\n    let all_files = filesystem::all_cheat_files(&tmp_pathbuf).join(\"\\n\");\n\n    let opts = FinderOpts {\n        suggestion_type: SuggestionType::MultipleSelections,\n        preview: Some(format!(\"cat '{tmp_path_str}/{{}}'\")),\n        header: Some(\"Select the cheatsheets you want to import with <TAB> then hit <Enter>\\nUse Ctrl-R for (de)selecting all\".to_string()),\n        preview_window: Some(\"right:30%\".to_string()),\n        ..Default::default()\n    };\n\n    let files = if should_import_all {\n        all_files\n    } else {\n        let (files, _) = finder\n            .call(opts, |stdin| {\n                stdin\n                    .write_all(all_files.as_bytes())\n                    .context(\"Unable to prompt cheats to import\")?;\n                Ok(())\n            })\n            .context(\"Failed to get cheatsheet files from finder\")?;\n        files\n    };\n\n    let to_folder = {\n        let mut p = cheat_pathbuf;\n        p.push(format!(\"{user}__{repo}\"));\n        p\n    };\n\n    for file in files.split('\\n') {\n        let from = {\n            let mut p = tmp_pathbuf.clone();\n            p.push(file);\n            p\n        };\n        let filename = file\n            .replace(&format!(\"{}{}\", &tmp_path_str, path::MAIN_SEPARATOR), \"\")\n            .replace(path::MAIN_SEPARATOR, \"__\");\n        let to = {\n            let mut p = to_folder.clone();\n            p.push(filename);\n            p\n        };\n        fs::create_dir_all(&to_folder).unwrap_or(());\n        fs::copy(&from, &to)\n            .with_context(|| format!(\"Failed to copy `{}` to `{}`\", &from.to_string(), &to.to_string()))?;\n    }\n\n    filesystem::remove_dir(&tmp_pathbuf)?;\n\n    eprintln!(\n        \"The following .cheat files were imported successfully:\\n{}\\n\\nThey are now located at {}\",\n        files,\n        to_folder.to_string()\n    );\n\n    Ok(())\n}\n"
  },
  {
    "path": "src/commands/repo/browse.rs",
    "content": "use crate::filesystem;\nuse crate::finder::structures::{Opts as FinderOpts, SuggestionType};\n\nuse crate::common::git;\nuse crate::prelude::*;\nuse std::fs;\n\npub fn main() -> Result<String> {\n    let finder = CONFIG.finder();\n\n    let repo_pathbuf = {\n        let mut p = filesystem::tmp_pathbuf()?;\n        p.push(\"featured\");\n        p\n    };\n\n    let repo_path_str = &repo_pathbuf.to_string();\n\n    let _ = filesystem::remove_dir(&repo_pathbuf);\n    filesystem::create_dir(&repo_pathbuf)?;\n\n    let (repo_url, _, _) = git::meta(\"denisidoro/cheats\");\n    git::shallow_clone(repo_url.as_str(), repo_path_str)\n        .with_context(|| format!(\"Failed to clone `{repo_url}`\"))?;\n\n    let feature_repos_file = {\n        let mut p = repo_pathbuf.clone();\n        p.push(\"featured_repos.txt\");\n        p\n    };\n\n    let repos = fs::read_to_string(feature_repos_file).context(\"Unable to fetch featured repositories\")?;\n\n    let opts = FinderOpts {\n        column: Some(1),\n        suggestion_type: SuggestionType::SingleSelection,\n        ..Default::default()\n    };\n\n    let (repo, _) = finder\n        .call(opts, |stdin| {\n            stdin\n                .write_all(repos.as_bytes())\n                .context(\"Unable to prompt featured repositories\")?;\n            Ok(())\n        })\n        .context(\"Failed to get repo URL from finder\")?;\n\n    filesystem::remove_dir(&repo_pathbuf)?;\n\n    Ok(repo)\n}\n"
  },
  {
    "path": "src/commands/repo/mod.rs",
    "content": "use crate::commands;\nuse crate::prelude::*;\nuse clap::{Args, Subcommand};\n\npub mod add;\npub mod browse;\n\n#[derive(Debug, Clone, Subcommand)]\npub enum RepoCommand {\n    /// Imports cheatsheets from a repo\n    Add {\n        /// A URI to a git repository containing .cheat files (\"user/repo\" will download cheats from github.com/user/repo)\n        uri: String,\n    },\n    /// Browses for featured cheatsheet repos\n    Browse,\n}\n\n#[derive(Debug, Clone, Args)]\npub struct Input {\n    #[clap(subcommand)]\n    pub cmd: RepoCommand,\n}\n\nimpl Runnable for Input {\n    fn run(&self) -> Result<()> {\n        match &self.cmd {\n            RepoCommand::Add { uri } => {\n                add::main(uri.clone())\n                    .with_context(|| format!(\"Failed to import cheatsheets from `{uri}`\"))?;\n                commands::core::main()\n            }\n            RepoCommand::Browse => {\n                let repo = browse::main().context(\"Failed to browse featured cheatsheets\")?;\n                add::main(repo.clone())\n                    .with_context(|| format!(\"Failed to import cheatsheets from `{repo}`\"))?;\n                commands::core::main()\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/commands/shell.rs",
    "content": "use std::fmt;\nuse std::fmt::Display;\n\nuse clap::Args;\n\nuse crate::common::shell::Shell;\nuse crate::prelude::*;\n\nimpl Display for Shell {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        let s = match self {\n            Self::Bash => \"bash\",\n            Self::Zsh => \"zsh\",\n            Self::Fish => \"fish\",\n            Self::Elvish => \"elvish\",\n            Self::Nushell => \"nushell\",\n            Self::Powershell => \"powershell\",\n        };\n\n        write!(f, \"{s}\")\n    }\n}\n\n#[derive(Debug, Clone, Args)]\npub struct Input {\n    #[clap(ignore_case = true, default_value_t = Shell::Bash)]\n    pub shell: Shell,\n}\n\nimpl Runnable for Input {\n    fn run(&self) -> Result<()> {\n        let shell = &self.shell;\n\n        let content = match shell {\n            Shell::Bash => include_str!(\"../../shell/navi.plugin.bash\"),\n            Shell::Zsh => include_str!(\"../../shell/navi.plugin.zsh\"),\n            Shell::Fish => include_str!(\"../../shell/navi.plugin.fish\"),\n            Shell::Elvish => include_str!(\"../../shell/navi.plugin.elv\"),\n            Shell::Nushell => include_str!(\"../../shell/navi.plugin.nu\"),\n            Shell::Powershell => include_str!(\"../../shell/navi.plugin.ps1\"),\n        };\n\n        println!(\"{content}\");\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "src/commands/temp.rs",
    "content": "use crate::commands::core::get_fetcher;\nuse crate::common::shell::{self, ShellSpawnError};\nuse crate::finder::structures::Opts as FinderOpts;\nuse crate::parser::Parser;\nuse crate::{deser, prelude::*};\nuse std::io::{self, Write};\n\npub fn main() -> Result<()> {\n    let _config = &CONFIG;\n    let _opts = FinderOpts::snippet_default();\n\n    let fetcher = get_fetcher()?;\n    let hash: u64 = 2087294461664323320;\n\n    let mut buf = vec![];\n    let mut parser = Parser::new(&mut buf, false);\n    parser.set_hash(hash);\n\n    let _res = fetcher\n        .fetch(&mut parser)\n        .context(\"Failed to parse variables intended for finder\")?;\n\n    let variables = parser.variables;\n    let item_str = String::from_utf8(buf)?;\n    let item = deser::raycast::read(&item_str)?;\n    dbg!(&item);\n\n    let x = variables.get_suggestion(&item.tags, \"local_branch\").expect(\"foo\");\n    dbg!(&x);\n\n    let suggestion_command = x.0.clone();\n    let child = shell::out()\n        .stdout(Stdio::piped())\n        .arg(&suggestion_command)\n        .spawn()\n        .map_err(|e| ShellSpawnError::new(suggestion_command, e))?;\n\n    let text = String::from_utf8(\n        child\n            .wait_with_output()\n            .context(\"Failed to wait and collect output from bash\")?\n            .stdout,\n    )\n    .context(\"Suggestions are invalid utf8\")?;\n\n    dbg!(&text);\n\n    Ok(())\n}\n\npub fn _main0() -> Result<()> {\n    let _config = &CONFIG;\n\n    let fetcher = get_fetcher()?;\n\n    let mut stdout = io::stdout();\n    let mut writer: Box<&mut dyn Write> = Box::new(&mut stdout);\n    let mut parser = Parser::new(&mut writer, false);\n\n    let _res = fetcher\n        .fetch(&mut parser)\n        .context(\"Failed to parse variables intended for finder\")?;\n\n    Ok(())\n}\n"
  },
  {
    "path": "src/common/clipboard.rs",
    "content": "use crate::common::shell::{self, ShellSpawnError, EOF};\nuse crate::prelude::*;\n\npub fn copy(text: String) -> Result<()> {\n    let cmd = r#\"\nexst() {\n   type \"$1\" &>/dev/null\n}\n\n_copy() {\n   if exst pbcopy; then\n      pbcopy\n   elif exst xclip; then\n      xclip -selection clipboard\n   elif exst clip.exe; then\n      clip.exe\n   else\n      exit 55\n   fi\n}\"#;\n\n    shell::out()\n        .arg(\n            format!(\n                r#\"{cmd} \n        read -r -d '' x <<'{EOF}'\n{text}\n{EOF}\n\necho -n \"$x\" | _copy\"#,\n            )\n            .as_str(),\n        )\n        .spawn()\n        .map_err(|e| ShellSpawnError::new(cmd, e))?\n        .wait()?;\n\n    Ok(())\n}\n"
  },
  {
    "path": "src/common/deps.rs",
    "content": "use crate::prelude::*;\n\npub trait HasDeps {\n    fn deps(&self) -> HashSet<TypeId> {\n        HashSet::new()\n    }\n}\n"
  },
  {
    "path": "src/common/fs.rs",
    "content": "use crate::prelude::*;\nuse remove_dir_all::remove_dir_all;\nuse std::ffi::OsStr;\nuse std::fs::{self, create_dir_all, File};\nuse std::io;\nuse thiserror::Error;\n\npub trait ToStringExt {\n    fn to_string(&self) -> String;\n}\n\nimpl ToStringExt for Path {\n    fn to_string(&self) -> String {\n        self.to_string_lossy().to_string()\n    }\n}\n\nimpl ToStringExt for OsStr {\n    fn to_string(&self) -> String {\n        self.to_string_lossy().to_string()\n    }\n}\n\n#[derive(Error, Debug)]\n#[error(\"Invalid path `{0}`\")]\npub struct InvalidPath(pub PathBuf);\n\n#[derive(Error, Debug)]\n#[error(\"Unable to read directory `{dir}`\")]\npub struct UnreadableDir {\n    dir: PathBuf,\n    #[source]\n    source: anyhow::Error,\n}\n\npub fn open(filename: &Path) -> Result<File> {\n    File::open(filename).with_context(|| {\n        let x = filename.to_string();\n        format!(\"Failed to open file {}\", &x)\n    })\n}\n\npub fn read_lines(filename: &Path) -> Result<impl Iterator<Item = Result<String>>> {\n    let file = open(filename)?;\n    Ok(io::BufReader::new(file)\n        .lines()\n        .map(|line| line.map_err(Error::from)))\n}\n\npub fn pathbuf_to_string(pathbuf: &Path) -> Result<String> {\n    Ok(pathbuf\n        .as_os_str()\n        .to_str()\n        .ok_or_else(|| InvalidPath(pathbuf.to_path_buf()))\n        .map(str::to_string)?)\n}\n\nfn follow_symlink(pathbuf: PathBuf) -> Result<PathBuf> {\n    fs::read_link(pathbuf.clone())\n        .map(|o| {\n            let o_str = o\n                .as_os_str()\n                .to_str()\n                .ok_or_else(|| InvalidPath(o.to_path_buf()))?;\n            if o_str.starts_with('.') {\n                let p = pathbuf\n                    .parent()\n                    .ok_or_else(|| anyhow!(\"`{}` has no parent\", pathbuf.display()))?;\n                let mut p = PathBuf::from(p);\n                p.push(o_str);\n                follow_symlink(p)\n            } else {\n                follow_symlink(o)\n            }\n        })\n        .unwrap_or(Ok(pathbuf))\n}\n\nfn exe_pathbuf() -> Result<PathBuf> {\n    let pathbuf = std::env::current_exe().context(\"Unable to acquire executable's path\")?;\n\n    #[cfg(target_family = \"windows\")]\n    let pathbuf = dunce::canonicalize(pathbuf)?;\n\n    debug!(current_exe = ?pathbuf);\n    follow_symlink(pathbuf)\n}\n\nfn exe_abs_string() -> Result<String> {\n    pathbuf_to_string(&exe_pathbuf()?)\n}\n\npub fn exe_string() -> String {\n    exe_abs_string().unwrap_or_else(|_| \"navi\".to_string())\n}\n\npub fn create_dir(path: &Path) -> Result<()> {\n    create_dir_all(path).with_context(|| {\n        format!(\n            \"Failed to create directory `{}`\",\n            pathbuf_to_string(path).expect(\"Unable to parse {path}\")\n        )\n    })\n}\n\npub fn remove_dir(path: &Path) -> Result<()> {\n    remove_dir_all(path).with_context(|| {\n        format!(\n            \"Failed to remove directory `{}`\",\n            pathbuf_to_string(path).expect(\"Unable to parse {path}\")\n        )\n    })\n}\n"
  },
  {
    "path": "src/common/git.rs",
    "content": "use crate::common::shell::ShellSpawnError;\nuse crate::prelude::*;\nuse std::process::Command;\n\npub fn shallow_clone(uri: &str, target: &str) -> Result<()> {\n    Command::new(\"git\")\n        .args([\"clone\", uri, target, \"--depth\", \"1\"])\n        .spawn()\n        .map_err(|e| ShellSpawnError::new(\"git clone\", e))?\n        .wait()\n        .context(\"Unable to git clone\")?;\n    Ok(())\n}\n\npub fn meta(uri: &str) -> (String, String, String) {\n    let actual_uri = if uri.contains(\"://\") || uri.contains('@') {\n        uri.to_string()\n    } else {\n        format!(\"https://github.com/{uri}\")\n    };\n\n    let uri_to_split = actual_uri.replace(':', \"/\");\n    let parts: Vec<&str> = uri_to_split.split('/').collect();\n    let user = parts[parts.len() - 2];\n    let repo = parts[parts.len() - 1].replace(\".git\", \"\");\n\n    (actual_uri, user.to_string(), repo)\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_meta_github_https() {\n        let (actual_uri, user, repo) = meta(\"https://github.com/denisidoro/navi\");\n        assert_eq!(actual_uri, \"https://github.com/denisidoro/navi\".to_string());\n        assert_eq!(user, \"denisidoro\".to_string());\n        assert_eq!(repo, \"navi\".to_string());\n    }\n\n    #[test]\n    fn test_meta_github_ssh() {\n        let (actual_uri, user, repo) = meta(\"git@github.com:denisidoro/navi.git\");\n        assert_eq!(actual_uri, \"git@github.com:denisidoro/navi.git\".to_string());\n        assert_eq!(user, \"denisidoro\".to_string());\n        assert_eq!(repo, \"navi\".to_string());\n    }\n\n    #[test]\n    fn test_meta_gitlab_https() {\n        let (actual_uri, user, repo) = meta(\"https://gitlab.com/user/repo.git\");\n        assert_eq!(actual_uri, \"https://gitlab.com/user/repo.git\".to_string());\n        assert_eq!(user, \"user\".to_string());\n        assert_eq!(repo, \"repo\".to_string());\n    }\n}\n"
  },
  {
    "path": "src/common/hash.rs",
    "content": "use std::hash::{Hash, Hasher};\n\nconst MAGIC_INIT: u64 = 0x811C_9DC5;\n\npub fn fnv<T: Hash>(x: &T) -> u64 {\n    let mut hasher = FnvHasher::new();\n    x.hash(&mut hasher);\n    hasher.finish()\n}\n\nstruct FnvHasher(u64);\n\nimpl FnvHasher {\n    fn new() -> Self {\n        FnvHasher(MAGIC_INIT)\n    }\n}\n\nimpl Hasher for FnvHasher {\n    fn finish(&self) -> u64 {\n        self.0\n    }\n\n    fn write(&mut self, bytes: &[u8]) {\n        for byte in bytes.iter() {\n            self.0 ^= u64::from(*byte);\n            self.0 = self.0.wrapping_mul(0x0100_0000_01b3);\n        }\n    }\n}\n"
  },
  {
    "path": "src/common/mod.rs",
    "content": "pub mod clipboard;\npub mod deps;\npub mod fs;\npub mod git;\npub mod hash;\npub mod shell;\npub mod terminal;\npub mod url;\n"
  },
  {
    "path": "src/common/shell.rs",
    "content": "use crate::prelude::*;\nuse clap::ValueEnum;\nuse std::process::Command;\nuse thiserror::Error;\n\npub const EOF: &str = \"NAVIEOF\";\n\n#[derive(Debug, Clone, ValueEnum)]\npub enum Shell {\n    Bash,\n    Zsh,\n    Fish,\n    Elvish,\n    Nushell,\n    Powershell,\n}\n\n#[derive(Error, Debug)]\n#[error(\"Failed to spawn child process `bash` to execute `{command}`\")]\npub struct ShellSpawnError {\n    command: String,\n    #[source]\n    source: anyhow::Error,\n}\n\nimpl ShellSpawnError {\n    pub fn new<SourceError>(command: impl Into<String>, source: SourceError) -> Self\n    where\n        SourceError: std::error::Error + Sync + Send + 'static,\n    {\n        ShellSpawnError {\n            command: command.into(),\n            source: source.into(),\n        }\n    }\n}\n\npub fn out() -> Command {\n    let words_str = CONFIG.shell();\n    let mut words_vec = shellwords::split(&words_str).expect(\"empty shell command\");\n    let mut words = words_vec.iter_mut();\n    let first_cmd = words.next().expect(\"absent shell binary\");\n    let mut cmd = Command::new(first_cmd);\n    cmd.args(words);\n    let dash_c = if words_str.contains(\"cmd.exe\") { \"/c\" } else { \"-c\" };\n    cmd.arg(dash_c);\n    cmd\n}\n"
  },
  {
    "path": "src/common/terminal.rs",
    "content": "use crate::prelude::*;\nuse crossterm::style;\nuse crossterm::terminal;\n\nuse std::process::Command;\n\nconst FALLBACK_WIDTH: u16 = 80;\n\nfn width_with_shell_out() -> Result<u16> {\n    let output = if cfg!(target_os = \"macos\") {\n        Command::new(\"stty\")\n            .arg(\"-f\")\n            .arg(\"/dev/stderr\")\n            .arg(\"size\")\n            .stderr(Stdio::inherit())\n            .output()?\n    } else {\n        Command::new(\"stty\")\n            .arg(\"size\")\n            .arg(\"-F\")\n            .arg(\"/dev/stderr\")\n            .stderr(Stdio::inherit())\n            .output()?\n    };\n\n    if let Some(0) = output.status.code() {\n        let stdout = String::from_utf8(output.stdout).expect(\"Invalid utf8 output from stty\");\n        let mut data = stdout.split_whitespace();\n        data.next();\n        return data\n            .next()\n            .expect(\"Not enough data\")\n            .parse::<u16>()\n            .map_err(|_| anyhow!(\"Invalid width\"));\n    }\n\n    Err(anyhow!(\"Invalid status code\"))\n}\n\npub fn width() -> u16 {\n    if let Ok((w, _)) = terminal::size() {\n        w\n    } else {\n        width_with_shell_out().unwrap_or(FALLBACK_WIDTH)\n    }\n}\n\npub fn parse_ansi(ansi: &str) -> Option<style::Color> {\n    style::Color::parse_ansi(&format!(\"5;{ansi}\"))\n}\n\n#[derive(Debug, Clone)]\npub struct Color(#[allow(unused)] pub style::Color); // suppress warning: field `0` is never read.\n\nimpl FromStr for Color {\n    type Err = &'static str;\n\n    fn from_str(ansi: &str) -> Result<Self, Self::Err> {\n        if let Some(c) = parse_ansi(ansi) {\n            Ok(Color(c))\n        } else {\n            Err(\"Invalid color\")\n        }\n    }\n}\n"
  },
  {
    "path": "src/common/url.rs",
    "content": "use crate::common::shell::{self, ShellSpawnError};\nuse crate::prelude::*;\nuse anyhow::Result;\nuse shell::EOF;\n\npub fn open(args: Vec<String>) -> Result<()> {\n    let url = args\n        .into_iter()\n        .next()\n        .ok_or_else(|| anyhow!(\"No URL specified\"))?;\n    let code = r#\"\nexst() {\n   type \"$1\" &>/dev/null\n}\n\n_open_url() { \n    local -r url=\"$1\"\n    if exst xdg-open; then\n        xdg-open \"$url\" &disown\n    elif exst open; then\n        echo \"$url\" | xargs -I% open \"%\"\n    else\n        exit 55\n    fi\n}\"#;\n    let cmd = format!(\n        r#\"{code}\n                \nread -r -d '' url <<'{EOF}'\n{url}\n{EOF}\n\n_open_url \"$url\"\"#,\n    );\n    shell::out()\n        .arg(cmd.as_str())\n        .spawn()\n        .map_err(|e| ShellSpawnError::new(cmd, e))?\n        .wait()?;\n    Ok(())\n}\n"
  },
  {
    "path": "src/config/cli.rs",
    "content": "use crate::commands;\nuse crate::finder::FinderChoice;\n\nuse clap::{crate_version, Parser, Subcommand};\n\n#[derive(Debug, Parser)]\n#[command(after_help = \"\\x1b[0;33mMORE INFO:\\x1b[0;0m\n    Please refer to \\x1b[0;32mhttps://github.com/denisidoro/navi\\x1b[0;0m\n\n\\x1b[0;33mENVIRONMENT VARIABLES:\\x1b[0m\n    \\x1b[0;32mNAVI_CONFIG\\x1b[0;0m            # path to config file\n    \\x1b[0;32mNAVI_CONFIG_YAML\\x1b[0;0m       # config file content\n\n\\x1b[0;33mFEATURE STABILITY:\\x1b[0m\n    \\x1b[0;32mexperimental\\x1b[0;0m           # may be removed or changed at any time\n    \\x1b[0;32mdeprecated\\x1b[0;0m             # may be removed in 3 months after first being deprecated\n\n\\x1b[0;33mCOMMON NAVI COMMANDS:\\x1b[0m\n    Run \\x1b[0;32mnavi fn welcome\\x1b[0;0m to browse the cheatsheet for navi itself\n\n\\x1b[0;33mEXAMPLES:\\x1b[0m\n    navi                                         # default behavior\n    navi fn welcome                              # show cheatsheets for navi itself\n    navi --print                                 # doesn't execute the snippet\n    navi --tldr docker                           # search for docker cheatsheets using tldr\n    navi --cheatsh docker                        # search for docker cheatsheets using cheatsh\n    navi --path '/some/dir:/other/dir'           # use .cheat files from custom paths\n    navi --query git                             # filter results by \\\"git\\\"\n    navi --query 'create db' --best-match        # autoselect the snippet that best matches a query\n    db=my navi --query 'create db' --best-match  # same, but set the value for the <name> variable\n    navi repo add denisidoro/cheats              # import cheats from a git repository\n    eval \\\"$(navi widget zsh)\\\"                    # load the zsh widget\n    navi --finder 'skim'                         # set skim as finder, instead of fzf\n    navi --fzf-overrides '--with-nth 1,2'        # show only the comment and tag columns\n    navi --fzf-overrides '--no-select-1'         # prevent autoselection in case of single line\n    navi --fzf-overrides-var '--no-select-1'     # same, but for variable selection\n    navi --fzf-overrides '--nth 1,2'             # only consider the first two columns for search\n    navi --fzf-overrides '--no-exact'            # use looser search algorithm\n    navi --tag-rules='git,!checkout'             # show non-checkout git snippets only\")]\n#[clap(version = crate_version!())]\npub(super) struct ClapConfig {\n    /// Colon-separated list of paths containing .cheat files\n    #[arg(short, long)]\n    pub path: Option<String>,\n\n    /// Instead of executing a snippet, prints it to stdout\n    #[arg(long)]\n    #[cfg(not(feature = \"disable-command-execution\"))]\n    pub print: bool,\n\n    /// Returns the best match\n    #[arg(long)]\n    pub best_match: bool,\n\n    /// Prevents variable interpolation\n    #[arg(long)]\n    pub prevent_interpolation: bool,\n\n    /// Searches for cheatsheets using the tldr-pages repository\n    #[arg(long)]\n    pub tldr: Option<String>,\n\n    /// [Experimental] Comma-separated list that acts as filter for tags. Parts starting with ! represent negation\n    #[arg(long)]\n    pub tag_rules: Option<String>,\n\n    /// Searches for cheatsheets using the cheat.sh repository\n    #[arg(long)]\n    pub cheatsh: Option<String>,\n\n    /// Prepopulates the search field\n    #[arg(short, long, allow_hyphen_values = true)]\n    pub query: Option<String>,\n\n    /// Finder overrides for snippet selection\n    #[arg(long, allow_hyphen_values = true)]\n    pub fzf_overrides: Option<String>,\n\n    /// Finder overrides for variable selection\n    #[arg(long, allow_hyphen_values = true)]\n    pub fzf_overrides_var: Option<String>,\n\n    /// Finder application to use\n    #[arg(long, ignore_case = true)]\n    pub finder: Option<FinderChoice>,\n\n    #[command(subcommand)]\n    pub cmd: Option<Command>,\n}\n\nimpl ClapConfig {\n    pub fn new() -> Self {\n        Self::parse()\n    }\n}\n\n// #[derive(Subcommand, Debug, Clone, Runnable, HasDeps)]\n#[derive(Subcommand, Debug, Clone)]\npub enum Command {\n    /// [Experimental] Calls internal functions\n    Fn(commands::func::Input),\n    /// Manages cheatsheet repositories\n    #[cfg(not(feature = \"disable-repo-management\"))]\n    Repo(commands::repo::Input),\n    /// Used for fzf's preview window when selecting snippets\n    #[command(hide = true)]\n    Preview(commands::preview::Input),\n    /// Used for fzf's preview window when selecting variable suggestions\n    #[command(hide = true)]\n    PreviewVar(commands::preview::var::Input),\n    /// Used for fzf's preview window when selecting variable suggestions\n    #[command(hide = true)]\n    PreviewVarStdin(commands::preview::var_stdin::Input),\n    /// Outputs shell widget source code\n    Widget(commands::shell::Input),\n    /// Shows info\n    Info(commands::info::Input),\n}\n\n#[derive(Debug)]\npub enum Source {\n    Filesystem(Option<String>),\n    Tldr(String),\n    Cheats(String),\n    Welcome,\n}\n\npub enum Action {\n    Print,\n    Execute,\n}\n"
  },
  {
    "path": "src/config/env.rs",
    "content": "use crate::env_var;\nuse crate::finder::FinderChoice;\nuse crate::prelude::*;\n\n#[derive(Debug)]\npub struct EnvConfig {\n    pub config_yaml: Option<String>,\n    pub config_path: Option<String>,\n    pub path: Option<String>,\n    pub finder: Option<FinderChoice>,\n    pub fzf_overrides: Option<String>,\n    pub fzf_overrides_var: Option<String>,\n}\n\nimpl EnvConfig {\n    pub fn new() -> Self {\n        Self {\n            config_yaml: env_var::get(env_var::CONFIG_YAML).ok(),\n            config_path: env_var::get(env_var::CONFIG).ok(),\n            path: env_var::get(env_var::PATH).ok(),\n            finder: env_var::get(env_var::FINDER)\n                .ok()\n                .and_then(|x| FinderChoice::from_str(&x).ok()),\n            fzf_overrides: env_var::get(env_var::FZF_OVERRIDES).ok(),\n            fzf_overrides_var: env_var::get(env_var::FZF_OVERRIDES_VAR).ok(),\n        }\n    }\n}\n"
  },
  {
    "path": "src/config/mod.rs",
    "content": "mod cli;\nmod env;\nmod yaml;\n\nuse crate::commands::func::Func;\nuse crate::finder::FinderChoice;\nuse crate::prelude::debug;\npub use cli::*;\nuse crossterm::style::Color;\nuse env::EnvConfig;\nuse yaml::YamlConfig;\n\nlazy_static! {\n    pub static ref CONFIG: Config = Config::new();\n}\n#[derive(Debug)]\npub struct Config {\n    yaml: YamlConfig,\n    clap: ClapConfig,\n    env: EnvConfig,\n}\n\nimpl Config {\n    pub fn new() -> Self {\n        let env = EnvConfig::new();\n        let yaml = YamlConfig::get(&env).unwrap_or_else(|e| {\n            eprintln!(\"Error parsing config file: {e}\");\n            eprintln!(\"Fallbacking to default one...\");\n            eprintln!();\n            YamlConfig::default()\n        });\n        let clap = ClapConfig::new();\n        Self { yaml, clap, env }\n    }\n\n    pub fn best_match(&self) -> bool {\n        self.clap.best_match\n    }\n\n    pub fn prevent_interpolation(&self) -> bool {\n        self.clap.prevent_interpolation\n    }\n\n    pub fn cmd(&self) -> Option<&Command> {\n        self.clap.cmd.as_ref()\n    }\n\n    pub fn source(&self) -> Source {\n        if let Some(query) = self.clap.tldr.clone() {\n            Source::Tldr(query)\n        } else if let Some(query) = self.clap.cheatsh.clone() {\n            Source::Cheats(query)\n        } else if let Some(Command::Fn(input)) = self.cmd() {\n            if let Func::Welcome = input.func {\n                Source::Welcome\n            } else {\n                Source::Filesystem(self.path())\n            }\n        } else {\n            Source::Filesystem(self.path())\n        }\n    }\n\n    pub fn path(&self) -> Option<String> {\n        if self.clap.path.is_some() {\n            debug!(\"CLAP PATH: {}\", self.clap.path.as_ref().unwrap());\n        }\n\n        self.clap\n            .path\n            .clone()\n            .or_else(|| {\n                if self.env.path.is_some() {\n                    debug!(\"ENV PATH: {}\", self.env.path.as_ref().unwrap());\n                }\n\n                self.env.path.clone()\n            })\n            .or_else(|| {\n                let p = self.yaml.cheats.paths.clone();\n\n                if p.is_empty() {\n                    None\n                } else {\n                    debug!(\"MULTIPLE YAML PATH: {}\", p.as_slice().join(\",\"));\n                    Some(p.join(crate::filesystem::JOIN_SEPARATOR))\n                }\n            })\n            .or_else(|| {\n                if self.yaml.cheats.path.is_some() {\n                    debug!(\n                        \"DEPRECATED UNIQUE YAML PATH: {}\",\n                        self.yaml.cheats.path.as_ref().unwrap()\n                    );\n                }\n\n                self.yaml.cheats.path.clone()\n            })\n            .or_else(|| {\n                debug!(\"No specific path given!\");\n\n                None\n            })\n    }\n\n    pub fn finder(&self) -> FinderChoice {\n        self.clap\n            .finder\n            .or(self.env.finder)\n            .unwrap_or(self.yaml.finder.command)\n    }\n\n    pub fn fzf_overrides(&self) -> Option<String> {\n        self.clap\n            .fzf_overrides\n            .clone()\n            .or_else(|| self.env.fzf_overrides.clone())\n            .or_else(|| self.yaml.finder.overrides.clone())\n    }\n\n    pub fn fzf_overrides_var(&self) -> Option<String> {\n        self.clap\n            .fzf_overrides_var\n            .clone()\n            .or_else(|| self.env.fzf_overrides_var.clone())\n            .or_else(|| self.yaml.finder.overrides_var.clone())\n    }\n\n    pub fn delimiter_var(&self) -> Option<String> {\n        self.yaml.finder.delimiter_var.clone()\n    }\n\n    pub fn tealdeer(&self) -> bool {\n        self.yaml.client.tealdeer\n    }\n\n    pub fn shell(&self) -> String {\n        self.yaml.shell.command.clone()\n    }\n\n    pub fn finder_shell(&self) -> String {\n        self.yaml\n            .shell\n            .finder_command\n            .clone()\n            .unwrap_or_else(|| self.yaml.shell.command.clone())\n    }\n\n    pub fn tag_rules(&self) -> Option<String> {\n        self.clap\n            .tag_rules\n            .clone()\n            .or_else(|| self.yaml.search.tags.clone())\n    }\n\n    pub fn tag_color(&self) -> Color {\n        self.yaml.style.tag.color.get()\n    }\n\n    pub fn comment_color(&self) -> Color {\n        self.yaml.style.comment.color.get()\n    }\n\n    pub fn snippet_color(&self) -> Color {\n        self.yaml.style.snippet.color.get()\n    }\n\n    pub fn tag_width_percentage(&self) -> u16 {\n        self.yaml.style.tag.width_percentage\n    }\n\n    pub fn comment_width_percentage(&self) -> u16 {\n        self.yaml.style.comment.width_percentage\n    }\n\n    pub fn snippet_width_percentage(&self) -> u16 {\n        self.yaml.style.snippet.width_percentage\n    }\n\n    pub fn tag_min_width(&self) -> u16 {\n        self.yaml.style.tag.min_width\n    }\n\n    pub fn comment_min_width(&self) -> u16 {\n        self.yaml.style.comment.min_width\n    }\n\n    pub fn snippet_min_width(&self) -> u16 {\n        self.yaml.style.snippet.min_width\n    }\n\n    #[cfg(feature = \"disable-command-execution\")]\n    fn print(&self) -> bool {\n        true\n    }\n\n    #[cfg(not(feature = \"disable-command-execution\"))]\n    fn print(&self) -> bool {\n        self.clap.print\n    }\n\n    pub fn action(&self) -> Action {\n        if self.print() {\n            Action::Print\n        } else {\n            Action::Execute\n        }\n    }\n\n    pub fn get_query(&self) -> Option<String> {\n        let q = self.clap.query.clone();\n        if q.is_some() {\n            return q;\n        }\n        if self.best_match() {\n            match self.source() {\n                Source::Tldr(q) => Some(q),\n                Source::Cheats(q) => Some(q),\n                _ => Some(String::from(\"\")),\n            }\n        } else {\n            None\n        }\n    }\n}\n\nimpl Default for Config {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n"
  },
  {
    "path": "src/config/yaml.rs",
    "content": "use super::env::EnvConfig;\nuse crate::common::fs;\nuse crate::filesystem::default_config_pathbuf;\nuse crate::finder::FinderChoice;\nuse crate::prelude::*;\nuse crossterm::style::Color as TerminalColor;\nuse serde::de;\n\n#[derive(Deserialize, Debug)]\npub struct Color(#[serde(deserialize_with = \"color_deserialize\")] TerminalColor);\n\nimpl Color {\n    pub fn get(&self) -> TerminalColor {\n        self.0\n    }\n}\n\nfn color_deserialize<'de, D>(deserializer: D) -> Result<TerminalColor, D::Error>\nwhere\n    D: de::Deserializer<'de>,\n{\n    let s: String = Deserialize::deserialize(deserializer)?;\n    TerminalColor::try_from(s.as_str())\n        .map_err(|_| de::Error::custom(format!(\"Failed to deserialize color: {s}\")))\n}\n\n#[derive(Deserialize, Debug)]\n#[serde(default)]\npub struct ColorWidth {\n    pub color: Color,\n    pub width_percentage: u16,\n    pub min_width: u16,\n}\n\n#[derive(Deserialize, Debug)]\n#[serde(default)]\npub struct Style {\n    pub tag: ColorWidth,\n    pub comment: ColorWidth,\n    pub snippet: ColorWidth,\n}\n\n#[derive(Deserialize, Debug)]\n#[serde(default)]\npub struct Finder {\n    #[serde(deserialize_with = \"finder_deserialize\")]\n    pub command: FinderChoice,\n    pub overrides: Option<String>,\n    pub overrides_var: Option<String>,\n    pub delimiter_var: Option<String>,\n}\n\nfn finder_deserialize<'de, D>(deserializer: D) -> Result<FinderChoice, D::Error>\nwhere\n    D: de::Deserializer<'de>,\n{\n    let s: String = Deserialize::deserialize(deserializer)?;\n    FinderChoice::from_str(s.to_lowercase().as_str())\n        .map_err(|_| de::Error::custom(format!(\"Failed to deserialize finder: {s}\")))\n}\n\n#[derive(Deserialize, Default, Debug)]\n#[serde(default)]\npub struct Cheats {\n    pub path: Option<String>,\n    pub paths: Vec<String>,\n}\n\n#[derive(Deserialize, Default, Debug)]\n#[serde(default)]\npub struct Search {\n    pub tags: Option<String>,\n}\n\n#[derive(Deserialize, Debug)]\n#[serde(default)]\npub struct Shell {\n    pub command: String,\n    pub finder_command: Option<String>,\n}\n\n#[derive(Deserialize, Debug)]\n#[serde(default)]\n#[derive(Default)]\npub struct Client {\n    pub tealdeer: bool,\n}\n\n#[derive(Deserialize, Debug)]\n#[serde(default)]\npub struct YamlConfig {\n    pub style: Style,\n    pub finder: Finder,\n    pub cheats: Cheats,\n    pub search: Search,\n    pub shell: Shell,\n    pub client: Client,\n    pub source: String, // <= The source of the current configuration\n}\n\nimpl YamlConfig {\n    fn from_str(text: &str) -> Result<Self> {\n        serde_yaml::from_str(text).map_err(|e| e.into())\n    }\n\n    fn from_path(path: &Path) -> Result<Self> {\n        let file = fs::open(path)?;\n        let reader = BufReader::new(file);\n        serde_yaml::from_reader(reader).map_err(|e| e.into())\n    }\n\n    pub fn get(env: &EnvConfig) -> Result<YamlConfig> {\n        if let Some(yaml) = env.config_yaml.as_ref() {\n            // We're getting the configuration from the environment variable `NAVI_CONFIG_YAML`\n            let mut cfg = Self::from_str(yaml)?;\n            cfg.source = \"ENV_NAVI_CONFIG_YAML\".to_string();\n\n            return Ok(cfg);\n        }\n        if let Some(path_str) = env.config_path.as_ref() {\n            // We're getting the configuration from a file given in the environment variable 'NAVI_CONFIG'\n\n            let p = PathBuf::from(path_str);\n            let mut cfg = YamlConfig::from_path(&p)?;\n            cfg.source = \"ENV_NAVI_CONFIG\".to_string();\n\n            return Ok(cfg);\n        }\n        if let Ok(p) = default_config_pathbuf() {\n            // We're getting the configuration from the default path\n\n            if p.exists() {\n                let mut cfg = YamlConfig::from_path(&p)?;\n                cfg.source = \"DEFAULT_CONFIG_FILE\".to_string();\n\n                return Ok(cfg);\n            }\n        }\n\n        // As no configuration has been found, we set the YAML configuration\n        // to be its default (built-in) value.\n        Ok(YamlConfig::default())\n    }\n}\n\nimpl Default for ColorWidth {\n    fn default() -> Self {\n        Self {\n            color: Color(TerminalColor::Blue),\n            width_percentage: 26,\n            min_width: 20,\n        }\n    }\n}\n\nimpl Default for Style {\n    fn default() -> Self {\n        Self {\n            tag: ColorWidth {\n                color: Color(TerminalColor::Cyan),\n                width_percentage: 26,\n                min_width: 20,\n            },\n            comment: ColorWidth {\n                color: Color(TerminalColor::Blue),\n                width_percentage: 42,\n                min_width: 45,\n            },\n            snippet: Default::default(),\n        }\n    }\n}\n\nimpl Default for Finder {\n    fn default() -> Self {\n        Self {\n            command: FinderChoice::Fzf,\n            overrides: None,\n            overrides_var: None,\n            delimiter_var: None,\n        }\n    }\n}\n\nimpl Default for Shell {\n    fn default() -> Self {\n        Self {\n            command: \"bash\".to_string(),\n            finder_command: None,\n        }\n    }\n}\n\nimpl Default for YamlConfig {\n    fn default() -> Self {\n        Self {\n            style: Default::default(),\n            finder: Default::default(),\n            cheats: Default::default(),\n            search: Default::default(),\n            shell: Default::default(),\n            client: Default::default(),\n            source: \"BUILT-IN\".to_string(),\n        }\n    }\n}\n"
  },
  {
    "path": "src/deser/mod.rs",
    "content": "use crate::prelude::*;\nuse unicode_width::UnicodeWidthStr;\n\npub mod raycast;\npub mod terminal;\n\nconst NEWLINE_ESCAPE_CHAR: char = '\\x15';\npub const LINE_SEPARATOR: &str = \" \\x15 \";\n\nlazy_static! {\n    pub static ref NEWLINE_REGEX: Regex = Regex::new(r\"\\\\\\s+\").expect(\"Invalid regex\");\n    pub static ref VAR_REGEX: Regex = Regex::new(r\"\\\\?<(\\w[\\w\\d\\-_]*)>\").expect(\"Invalid regex\");\n}\n\npub fn with_new_lines(txt: String) -> String {\n    txt.replace(LINE_SEPARATOR, \"\\n\")\n}\n\npub fn fix_newlines(txt: &str) -> String {\n    if txt.contains(NEWLINE_ESCAPE_CHAR) {\n        (*NEWLINE_REGEX)\n            .replace_all(txt.replace(LINE_SEPARATOR, \"  \").as_str(), \"\")\n            .to_string()\n    } else {\n        txt.to_string()\n    }\n}\n\nfn limit_str(text: &str, length: usize) -> String {\n    let len = UnicodeWidthStr::width(text);\n    if len <= length {\n        format!(\"{}{}\", text, \" \".repeat(length - len))\n    } else {\n        let mut new_length = length;\n        let mut actual_length = 9999;\n        let mut txt = text.to_owned();\n        while actual_length >= length {\n            txt = txt.chars().take(new_length - 1).collect::<String>();\n            actual_length = UnicodeWidthStr::width(txt.as_str());\n            new_length -= 1;\n        }\n        format!(\"{}…{}\", txt, \" \".repeat(length - actual_length - 1))\n    }\n}\n"
  },
  {
    "path": "src/deser/raycast.rs",
    "content": "use super::*;\nuse crate::structures::item::Item;\n\nconst FIELD_SEP_ESCAPE_CHAR: char = '\\x16';\n\npub fn write(item: &Item) -> String {\n    format!(\n        \"{hash}{delimiter}{tags}{delimiter}{comment}{delimiter}{icon}{delimiter}{snippet}\\n\",\n        hash = item.hash(),\n        tags = item.tags,\n        comment = item.comment,\n        delimiter = FIELD_SEP_ESCAPE_CHAR,\n        icon = item.icon.clone().unwrap_or_default(),\n        snippet = &item.snippet.trim_end_matches(LINE_SEPARATOR),\n    )\n}\n\npub fn read(line: &str) -> Result<Item> {\n    let mut parts = line.split(FIELD_SEP_ESCAPE_CHAR);\n    let hash: u64 = parts\n        .next()\n        .context(\"no hash\")?\n        .parse()\n        .context(\"hash not a u64\")?;\n    let tags = parts.next().context(\"no tags\")?.into();\n    let comment = parts.next().context(\"no comment\")?.into();\n    let icon_str = parts.next().context(\"no icon\")?;\n    let snippet = parts.next().context(\"no snippet\")?.into();\n\n    let icon = if icon_str.is_empty() {\n        None\n    } else {\n        Some(icon_str.into())\n    };\n\n    let item = Item {\n        tags,\n        comment,\n        icon,\n        snippet,\n        ..Default::default()\n    };\n\n    if item.hash() != hash {\n        dbg!(&item.hash());\n        dbg!(hash);\n        Err(anyhow!(\"Incorrect hash\"))\n    } else {\n        Ok(item)\n    }\n}\n"
  },
  {
    "path": "src/deser/terminal.rs",
    "content": "use super::*;\nuse crate::common::terminal;\nuse crate::structures::item::Item;\nuse crossterm::style::{style, Stylize};\nuse std::cmp::max;\n\npub fn get_widths() -> (usize, usize, usize) {\n    let width = terminal::width();\n    let tag_width_percentage = max(\n        CONFIG.tag_min_width(),\n        width * CONFIG.tag_width_percentage() / 100,\n    );\n    let comment_width_percentage = max(\n        CONFIG.comment_min_width(),\n        width * CONFIG.comment_width_percentage() / 100,\n    );\n    let snippet_width_percentage = max(\n        CONFIG.snippet_min_width(),\n        width * CONFIG.snippet_width_percentage() / 100,\n    );\n    (\n        usize::from(tag_width_percentage),\n        usize::from(comment_width_percentage),\n        usize::from(snippet_width_percentage),\n    )\n}\n\npub const DELIMITER: &str = r\"  ⠀\";\n\nlazy_static! {\n    pub static ref COLUMN_WIDTHS: (usize, usize, usize) = get_widths();\n}\n\npub fn write(item: &Item) -> String {\n    let (tag_width_percentage, comment_width_percentage, snippet_width_percentage) = *COLUMN_WIDTHS;\n    format!(\n            \"{tags_short}{delimiter}{comment_short}{delimiter}{snippet_short}{delimiter}{tags}{delimiter}{comment}{delimiter}{snippet}{delimiter}{file_index}{delimiter}\\n\",\n            tags_short = style(limit_str(&item.tags, tag_width_percentage)).with(CONFIG.tag_color()),\n            comment_short = style(limit_str(&item.comment, comment_width_percentage)).with(CONFIG.comment_color()),\n            snippet_short = style(limit_str(&fix_newlines(&item.snippet), snippet_width_percentage)).with(CONFIG.snippet_color()),\n            tags = item.tags,\n            comment = item.comment,\n            delimiter = DELIMITER,\n            snippet = &item.snippet.trim_end_matches(LINE_SEPARATOR),\n            file_index = item.file_index.unwrap_or(0),\n        )\n}\n\npub fn read(raw_snippet: &str, is_single: bool) -> Result<(&str, Item)> {\n    let mut lines = raw_snippet.split('\\n');\n    let key = if is_single {\n        \"enter\"\n    } else {\n        lines\n            .next()\n            .context(\"Key was promised but not present in `selections`\")?\n    };\n\n    let mut parts = lines\n        .next()\n        .context(\"No more parts in `selections`\")?\n        .split(DELIMITER)\n        .skip(3);\n\n    let tags = parts.next().unwrap_or(\"\").into();\n    let comment = parts.next().unwrap_or(\"\").into();\n    let snippet = parts.next().unwrap_or(\"\").into();\n    let file_index = parts.next().unwrap_or(\"\").parse().ok();\n\n    let item = Item {\n        tags,\n        comment,\n        snippet,\n        file_index,\n        ..Default::default()\n    };\n\n    Ok((key, item))\n}\n"
  },
  {
    "path": "src/env_var.rs",
    "content": "use crate::prelude::*;\npub use env::remove_var as remove;\npub use env::set_var as set;\npub use env::var as get;\nuse std::env;\n\npub const PREVIEW_INITIAL_SNIPPET: &str = \"NAVI_PREVIEW_INITIAL_SNIPPET\";\npub const PREVIEW_TAGS: &str = \"NAVI_PREVIEW_TAGS\";\npub const PREVIEW_COMMENT: &str = \"NAVI_PREVIEW_COMMENT\";\npub const PREVIEW_COLUMN: &str = \"NAVI_PREVIEW_COLUMN\";\npub const PREVIEW_DELIMITER: &str = \"NAVI_PREVIEW_DELIMITER\";\npub const PREVIEW_MAP: &str = \"NAVI_PREVIEW_MAP\";\n\npub const PATH: &str = \"NAVI_PATH\";\npub const FZF_OVERRIDES: &str = \"NAVI_FZF_OVERRIDES\";\npub const FZF_OVERRIDES_VAR: &str = \"NAVI_FZF_OVERRIDES_VAR\";\npub const FINDER: &str = \"NAVI_FINDER\";\n\npub const CONFIG: &str = \"NAVI_CONFIG\";\npub const CONFIG_YAML: &str = \"NAVI_CONFIG_YAML\";\n\npub fn parse<T: FromStr>(varname: &str) -> Option<T> {\n    if let Ok(x) = env::var(varname) {\n        x.parse::<T>().ok()\n    } else {\n        None\n    }\n}\n\npub fn must_get(name: &str) -> String {\n    if let Ok(v) = env::var(name) {\n        v\n    } else {\n        panic!(\"{name} not set\")\n    }\n}\n\npub fn escape(name: &str) -> String {\n    name.replace('-', \"_\")\n}\n"
  },
  {
    "path": "src/filesystem.rs",
    "content": "pub use crate::common::fs::{create_dir, exe_string, read_lines, remove_dir};\nuse crate::env_var;\nuse crate::parser::Parser;\nuse crate::prelude::*;\n\nuse crate::structures::fetcher;\nuse etcetera::BaseStrategy;\nuse regex::Regex;\n\nuse std::cell::RefCell;\nuse std::path::MAIN_SEPARATOR;\n\nuse walkdir::WalkDir;\n\n/// Multiple paths are joint by a platform-specific separator.\n/// FIXME: it's actually incorrect to assume a path doesn't containing this separator\n#[cfg(target_family = \"windows\")]\npub const JOIN_SEPARATOR: &str = \";\";\n#[cfg(not(target_family = \"windows\"))]\npub const JOIN_SEPARATOR: &str = \":\";\n\npub fn all_cheat_files(path: &Path) -> Vec<String> {\n    WalkDir::new(path)\n        .follow_links(true)\n        .into_iter()\n        .filter_map(|e| e.ok())\n        .map(|e| e.path().to_str().unwrap_or(\"\").to_string())\n        .filter(|e| e.ends_with(\".cheat\") || e.ends_with(\".cheat.md\"))\n        .collect::<Vec<String>>()\n}\n\nfn paths_from_path_param(env_var: &str) -> impl Iterator<Item = &str> {\n    env_var.split(JOIN_SEPARATOR).filter(|folder| folder != &\"\")\n}\n\nfn compiled_default_path(path: Option<&str>) -> Option<PathBuf> {\n    match path {\n        Some(path) => {\n            let path = if path.contains(MAIN_SEPARATOR) {\n                path.split(MAIN_SEPARATOR).next().unwrap()\n            } else {\n                path\n            };\n            let path = Path::new(path);\n            if path.exists() {\n                Some(path.to_path_buf())\n            } else {\n                None\n            }\n        }\n        None => None,\n    }\n}\n\npub fn default_cheat_pathbuf() -> Result<PathBuf> {\n    let mut pathbuf = get_data_dir_by_platform()?;\n\n    pathbuf.push(\"navi\");\n    pathbuf.push(\"cheats\");\n\n    if pathbuf.exists() {\n        if let Some(path) = compiled_default_path(option_env!(\"NAVI_PATH\")) {\n            pathbuf = path;\n        }\n    }\n    Ok(pathbuf)\n}\n\npub fn default_config_pathbuf() -> Result<PathBuf> {\n    let mut pathbuf = get_config_dir_by_platform()?;\n\n    pathbuf.push(\"navi\");\n    pathbuf.push(\"config.yaml\");\n\n    if !pathbuf.exists() {\n        if let Some(path) = compiled_default_path(option_env!(\"NAVI_CONFIG\")) {\n            pathbuf = path;\n        }\n    }\n    Ok(pathbuf)\n}\n\npub fn cheat_paths(path: Option<String>) -> Result<String> {\n    if let Some(p) = path {\n        Ok(p)\n    } else {\n        Ok(default_cheat_pathbuf()?.to_string())\n    }\n}\n\n////////////////////////////////////////////////////////////////////////////////////////////////////\n//\n// Here are other functions, unrelated to CLI commands (or at least not directly related)\n//\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\n/// Returns the data dir computed for each platform.\n///\n/// We are currently handling two cases: When the platform is `macOS` and when the platform isn't (including `Windows` and `Linux/Unix` platforms)\nfn get_data_dir_by_platform() -> Result<PathBuf> {\n    if cfg!(target_os = \"macos\") {\n        let base_dirs = etcetera::base_strategy::Apple::new()?;\n\n        Ok(base_dirs.data_dir())\n    } else {\n        let base_dirs = etcetera::choose_base_strategy()?;\n\n        Ok(base_dirs.data_dir())\n    }\n}\n\n/// Returns the config dir computed for each platform.\n///\n/// We are currently handling two cases: When the platform is `macOS` and when the platform isn't (including `Windows` and `Linux/Unix` platforms)\nfn get_config_dir_by_platform() -> Result<PathBuf> {\n    if cfg!(target_os = \"macos\") {\n        let base_dirs = etcetera::base_strategy::Apple::new()?;\n\n        Ok(base_dirs.config_dir())\n    } else {\n        let base_dirs = etcetera::choose_base_strategy()?;\n\n        Ok(base_dirs.config_dir())\n    }\n}\n\npub fn tmp_pathbuf() -> Result<PathBuf> {\n    let mut root = default_cheat_pathbuf()?;\n    root.push(\"tmp\");\n    Ok(root)\n}\n\nfn interpolate_paths(paths: String) -> String {\n    let re = Regex::new(r#\"\\$\\{?[a-zA-Z_][a-zA-Z_0-9]*\"#).unwrap();\n    let mut newtext = paths.to_string();\n    for capture in re.captures_iter(&paths) {\n        if let Some(c) = capture.get(0) {\n            let varname = c.as_str().replace(['$', '{', '}'], \"\");\n            if let Ok(replacement) = &env_var::get(&varname) {\n                newtext = newtext\n                    .replace(&format!(\"${varname}\"), replacement)\n                    .replace(&format!(\"${{{varname}}}\"), replacement);\n            }\n        }\n    }\n    newtext\n}\n\n#[derive(Debug)]\npub struct Fetcher {\n    path: Option<String>,\n    files: RefCell<Vec<String>>,\n}\n\nimpl Fetcher {\n    pub fn new(path: Option<String>) -> Self {\n        Self {\n            path,\n            files: Default::default(),\n        }\n    }\n}\n\nimpl fetcher::Fetcher for Fetcher {\n    fn fetch(&self, parser: &mut Parser) -> Result<bool> {\n        let mut found_something = false;\n\n        let path = self.path.clone();\n        let paths = cheat_paths(path);\n\n        if paths.is_err() {\n            return Ok(false);\n        };\n\n        let paths = paths.expect(\"Unable to get paths\");\n        let interpolated_paths = interpolate_paths(paths);\n        let folders = paths_from_path_param(&interpolated_paths);\n\n        let home_regex = Regex::new(r\"^~\").unwrap();\n        let home = etcetera::home_dir().ok();\n\n        // parser.filter = self.tag_rules.as_ref().map(|r| gen_lists(r.as_str()));\n\n        for folder in folders {\n            let interpolated_folder = match &home {\n                Some(h) => home_regex.replace(folder, h.to_string_lossy()).to_string(),\n                None => folder.to_string(),\n            };\n            let folder_pathbuf = PathBuf::from(interpolated_folder);\n            let cheat_files = all_cheat_files(&folder_pathbuf);\n            debug!(\"read cheat files in `{folder_pathbuf:?}`: {cheat_files:#?}\");\n            for file in cheat_files {\n                self.files.borrow_mut().push(file.clone());\n                let index = self.files.borrow().len() - 1;\n                let read_file_result = {\n                    let path = PathBuf::from(&file);\n                    let lines = read_lines(&path)?;\n                    parser.read_lines(lines, &file, Some(index))\n                };\n\n                if read_file_result.is_ok() && !found_something {\n                    found_something = true\n                }\n            }\n        }\n\n        debug!(\"FilesystemFetcher = {self:#?}\");\n        Ok(found_something)\n    }\n\n    fn files(&self) -> Vec<String> {\n        self.files.borrow().clone()\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    /* TODO\n\n    use crate::finder::structures::{Opts as FinderOpts, SuggestionType};\n    use crate::writer;\n    use std::process::{Command, Stdio};\n\n    #[test]\n    fn test_read_file() {\n        let path = \"tests/cheats/ssh.cheat\";\n        let mut variables = VariableMap::new();\n        let mut child = Command::new(\"cat\")\n            .stdin(Stdio::piped())\n            .stdout(Stdio::null())\n            .spawn()\n            .unwrap();\n        let child_stdin = child.stdin.as_mut().unwrap();\n        let mut visited_lines: HashSet<u64> = HashSet::new();\n        let mut writer: Box<dyn Writer> = Box::new(writer::terminal::Writer::new());\n        read_file(\n            path,\n            0,\n            &mut variables,\n            &mut visited_lines,\n            &mut *writer,\n            child_stdin,\n        )\n        .unwrap();\n        let expected_suggestion = (\n            r#\" echo -e \"$(whoami)\\nroot\" \"#.to_string(),\n            Some(FinderOpts {\n                header_lines: 0,\n                column: None,\n                delimiter: None,\n                suggestion_type: SuggestionType::SingleSelection,\n                ..Default::default()\n            }),\n        );\n        let actual_suggestion = variables.get_suggestion(\"ssh\", \"user\");\n        assert_eq!(Some(&expected_suggestion), actual_suggestion);\n    }\n    */\n\n    #[test]\n    fn splitting_of_dirs_param_may_not_contain_empty_items() {\n        // Trailing colon indicates potential extra path. Split returns an empty item for it. This empty item should be filtered away, which is what this test checks.\n        let given_path_config = \"SOME_PATH:ANOTHER_PATH:\";\n\n        let found_paths = paths_from_path_param(given_path_config);\n\n        let mut expected_paths = vec![\"SOME_PATH\", \"ANOTHER_PATH\"].into_iter();\n\n        for found in found_paths {\n            let expected = expected_paths.next().unwrap();\n            assert_eq!(found, expected)\n        }\n    }\n\n    #[test]\n    fn test_default_config_pathbuf() {\n        let base_dirs = etcetera::choose_base_strategy().expect(\"could not determine base directories\");\n\n        let expected = {\n            let mut e = base_dirs.config_dir();\n            e.push(\"navi\");\n            e.push(\"config.yaml\");\n            e.to_string_lossy().to_string()\n        };\n\n        let config = default_config_pathbuf().expect(\"could not find default config path\");\n\n        assert_eq!(expected, config.to_string_lossy().to_string())\n    }\n\n    #[test]\n    fn test_default_cheat_pathbuf() {\n        let base_dirs = etcetera::choose_base_strategy().expect(\"could not determine base directories\");\n\n        let expected = {\n            let mut e = base_dirs.data_dir();\n            e.push(\"navi\");\n            e.push(\"cheats\");\n            e.to_string_lossy().to_string()\n        };\n\n        let cheats = default_cheat_pathbuf().expect(\"could not find default config path\");\n\n        assert_eq!(expected, cheats.to_string_lossy().to_string())\n    }\n\n    #[test]\n    #[cfg(target_family = \"windows\")]\n    fn multiple_paths() {\n        let p = r#\"C:\\Users\\Administrator\\AppData\\Roaming\\navi\\config.yaml\"#;\n        let paths = &[p; 2].join(JOIN_SEPARATOR);\n        assert_eq!(paths_from_path_param(paths).collect::<Vec<_>>(), [p; 2]);\n    }\n}\n"
  },
  {
    "path": "src/finder/mod.rs",
    "content": "use crate::deser;\nuse crate::prelude::*;\nuse std::io::Write;\nuse std::process::{self, Output};\nuse std::process::{Command, Stdio};\npub mod structures;\nuse clap::ValueEnum;\npub use post::process;\nuse structures::Opts;\nuse structures::SuggestionType;\n\nconst MIN_FZF_VERSION_MAJOR: u32 = 0;\nconst MIN_FZF_VERSION_MINOR: u32 = 23;\nconst MIN_FZF_VERSION_PATCH: u32 = 1;\n\nmod post;\n\n#[derive(Debug, Clone, Copy, Deserialize, ValueEnum)]\npub enum FinderChoice {\n    Fzf,\n    Skim,\n}\n\nimpl FromStr for FinderChoice {\n    type Err = &'static str;\n\n    fn from_str(s: &str) -> Result<Self, Self::Err> {\n        match s {\n            \"fzf\" => Ok(FinderChoice::Fzf),\n            \"skim\" => Ok(FinderChoice::Skim),\n            _ => Err(\"no match\"),\n        }\n    }\n}\n\nfn parse(out: Output, opts: Opts) -> Result<String> {\n    let text = match out.status.code() {\n        Some(0) | Some(1) | Some(2) => {\n            String::from_utf8(out.stdout).context(\"Invalid utf8 received from finder\")?\n        }\n        Some(130) => process::exit(130),\n        _ => {\n            let err = String::from_utf8(out.stderr)\n                .unwrap_or_else(|_| \"<stderr contains invalid UTF-8>\".to_owned());\n            panic!(\"External command failed:\\n {err}\")\n        }\n    };\n\n    let output = post::parse_output_single(text, opts.suggestion_type)?;\n    post::process(output, opts.column, opts.delimiter.as_deref(), opts.map)\n}\n\nimpl FinderChoice {\n    fn check_fzf_version() -> Option<(u32, u32, u32)> {\n        let output = Command::new(\"fzf\").arg(\"--version\").output().ok()?.stdout;\n        let version_string = String::from_utf8(output).ok()?;\n        let version_parts: Vec<_> = version_string.split('.').collect();\n        if version_parts.len() == 3 {\n            let major = version_parts[0].parse().ok()?;\n            let minor = version_parts[1].parse().ok()?;\n            let patch = version_parts[2].split_whitespace().next()?.parse().ok()?;\n            Some((major, minor, patch))\n        } else {\n            None\n        }\n    }\n\n    pub fn call<F, R>(&self, finder_opts: Opts, stdin_fn: F) -> Result<(String, R)>\n    where\n        F: Fn(&mut dyn Write) -> Result<R>,\n    {\n        let finder_str = match self {\n            Self::Fzf => \"fzf\",\n            Self::Skim => \"sk\",\n        };\n\n        if let Self::Fzf = self {\n            if let Some((major, minor, patch)) = Self::check_fzf_version() {\n                if major == MIN_FZF_VERSION_MAJOR\n                    && minor < MIN_FZF_VERSION_MINOR\n                    && patch < MIN_FZF_VERSION_PATCH\n                {\n                    eprintln!(\n                        \"Warning: Fzf version {major}.{minor} does not support the preview window layout used by navi.\",\n                    );\n                    eprintln!(\n                        \"Consider updating Fzf to a version >= {MIN_FZF_VERSION_MAJOR}.{MIN_FZF_VERSION_MINOR}.{MIN_FZF_VERSION_PATCH} or use a compatible layout.\",\n                    );\n                    process::exit(1);\n                }\n            }\n        }\n\n        let mut command = Command::new(finder_str);\n        let opts = finder_opts.clone();\n\n        let preview_height = match self {\n            FinderChoice::Skim => 3,\n            _ => 2,\n        };\n\n        let bindings = if opts.suggestion_type == SuggestionType::MultipleSelections {\n            \",ctrl-r:toggle-all\"\n        } else {\n            \"\"\n        };\n\n        command.args([\n            \"--preview\",\n            \"\",\n            \"--preview-window\",\n            format!(\"up:{preview_height}:nohidden\").as_str(),\n            \"--delimiter\",\n            deser::terminal::DELIMITER.to_string().as_str(),\n            \"--ansi\",\n            \"--bind\",\n            format!(\"ctrl-j:down,ctrl-k:up{bindings}\").as_str(),\n            \"--exact\",\n        ]);\n\n        if !opts.show_all_columns {\n            command.args([\"--with-nth\", \"1,2,3\"]);\n        }\n\n        if !opts.prevent_select1 {\n            if let Self::Fzf = self {\n                command.arg(\"--select-1\");\n            }\n        }\n\n        match opts.suggestion_type {\n            SuggestionType::MultipleSelections => {\n                command.arg(\"--multi\");\n            }\n            SuggestionType::Disabled => {\n                if let Self::Fzf = self {\n                    command.args([\"--print-query\", \"--no-select-1\"]);\n                };\n            }\n            SuggestionType::SnippetSelection => {\n                command.args([\"--expect\", \"ctrl-y,ctrl-o,enter\"]);\n            }\n            SuggestionType::SingleRecommendation => {\n                command.args([\"--print-query\", \"--expect\", \"tab,enter\"]);\n            }\n            _ => {}\n        }\n\n        if let Some(p) = opts.preview {\n            command.args([\"--preview\", &p]);\n        }\n\n        if let Some(q) = opts.query {\n            command.args([\"--query\", &q]);\n        }\n\n        if let Some(f) = opts.filter {\n            command.args([\"--filter\", &f]);\n        }\n\n        if let Some(d) = opts.delimiter {\n            command.args([\"--delimiter\", &d]);\n        }\n\n        if let Some(h) = opts.header {\n            command.args([\"--header\", &h]);\n        }\n\n        if let Some(p) = opts.prompt {\n            command.args([\"--prompt\", &p]);\n        }\n\n        if let Some(pw) = opts.preview_window {\n            command.args([\"--preview-window\", &pw]);\n        }\n\n        if opts.header_lines > 0 {\n            command.args([\"--header-lines\", format!(\"{}\", opts.header_lines).as_str()]);\n        }\n\n        if let Some(o) = opts.overrides {\n            shellwords::split(&o)?\n                .into_iter()\n                .filter(|s| !s.is_empty())\n                .for_each(|s| {\n                    command.arg(s);\n                });\n        }\n\n        command\n            .env(\"SHELL\", CONFIG.finder_shell())\n            .stdin(Stdio::piped())\n            .stdout(Stdio::piped());\n        debug!(cmd = ?command);\n\n        let child = command.spawn();\n\n        let mut child = match child {\n            Ok(x) => x,\n            Err(_) => {\n                let repo = match self {\n                    Self::Fzf => \"https://github.com/junegunn/fzf\",\n                    Self::Skim => \"https://github.com/lotabout/skim\",\n                };\n                eprintln!(\n                    \"navi was unable to call {cmd}.\n                Please make sure it's correctly installed.\n                Refer to {repo} for more info.\",\n                    cmd = &finder_str,\n                    repo = repo\n                );\n                process::exit(33)\n            }\n        };\n\n        let stdin = child\n            .stdin\n            .as_mut()\n            .ok_or_else(|| anyhow!(\"Unable to acquire stdin of finder\"))?;\n\n        let mut writer: Box<&mut dyn Write> = Box::new(stdin);\n\n        let return_value = stdin_fn(&mut writer).context(\"Failed to pass data to finder\")?;\n\n        let out = child.wait_with_output().context(\"Failed to wait for finder\")?;\n\n        let output = parse(out, finder_opts).context(\"Unable to get output\")?;\n        Ok((output, return_value))\n    }\n}\n"
  },
  {
    "path": "src/finder/post.rs",
    "content": "use crate::common::shell;\nuse crate::finder::structures::SuggestionType;\nuse crate::prelude::*;\nuse shell::EOF;\nuse std::process::Stdio;\n\nfn apply_map(text: String, map_fn: Option<String>) -> Result<String> {\n    if let Some(m) = map_fn {\n        let cmd = if CONFIG.shell().contains(\"fish\") {\n            format!(r#\"printf \"%s\" \"{text}\" | {m}\"#)\n        } else {\n            format!(\n                r#\"_navi_input() {{\ncat <<'{EOF}'\n{text}\n{EOF}\n}}\n\n_navi_map_fn() {{\n  {m}\n}}\n\n_navi_nonewline() {{\n  printf \"%s\" \"$(cat)\"\n}}\n\n_navi_input | _navi_map_fn | _navi_nonewline\"#\n            )\n        };\n\n        let output = shell::out()\n            .arg(cmd.as_str())\n            .stderr(Stdio::inherit())\n            .output()\n            .context(\"Failed to execute map function\")?;\n\n        String::from_utf8(output.stdout).context(\"Invalid utf8 output for map function\")\n    } else {\n        Ok(text)\n    }\n}\n\nfn get_column(text: String, column: Option<u8>, delimiter: Option<&str>) -> String {\n    if let Some(c) = column {\n        let mut result = String::from(\"\");\n        let re = regex::Regex::new(delimiter.unwrap_or(r\"\\s\\s+\")).expect(\"Invalid regex\");\n        for line in text.split('\\n') {\n            if (line).is_empty() {\n                continue;\n            }\n            let mut parts = re.split(line).skip((c - 1) as usize);\n            if !result.is_empty() {\n                result.push('\\n');\n            }\n            result.push_str(parts.next().unwrap_or(\"\"));\n        }\n        result\n    } else {\n        text\n    }\n}\n\npub fn process(\n    text: String,\n    column: Option<u8>,\n    delimiter: Option<&str>,\n    map_fn: Option<String>,\n) -> Result<String> {\n    apply_map(get_column(text, column, delimiter), map_fn)\n}\n\npub(super) fn parse_output_single(mut text: String, suggestion_type: SuggestionType) -> Result<String> {\n    Ok(match suggestion_type {\n        SuggestionType::SingleSelection => text\n            .lines()\n            .next()\n            .context(\"No sufficient data for single selection\")?\n            .to_string(),\n        SuggestionType::MultipleSelections | SuggestionType::Disabled | SuggestionType::SnippetSelection => {\n            let len = text.len();\n            if len > 1 {\n                text.truncate(len - 1);\n            }\n            text\n        }\n        SuggestionType::SingleRecommendation => {\n            let lines: Vec<&str> = text.lines().collect();\n\n            match (lines.first(), lines.get(1), lines.get(2)) {\n                (Some(one), Some(termination), Some(two))\n                    if *termination == \"enter\" || termination.is_empty() =>\n                {\n                    if two.is_empty() {\n                        (*one).to_string()\n                    } else {\n                        (*two).to_string()\n                    }\n                }\n                (Some(one), Some(termination), None) if *termination == \"enter\" || termination.is_empty() => {\n                    (*one).to_string()\n                }\n                (Some(one), Some(termination), _) if *termination == \"tab\" => (*one).to_string(),\n                _ => \"\".to_string(),\n            }\n        }\n    })\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_parse_output1() {\n        let text = \"palo\\n\".to_string();\n        let output = parse_output_single(text, SuggestionType::SingleSelection).unwrap();\n        assert_eq!(output, \"palo\");\n    }\n\n    #[test]\n    fn test_parse_output2() {\n        let text = \"\\nenter\\npalo\".to_string();\n        let output = parse_output_single(text, SuggestionType::SingleRecommendation).unwrap();\n        assert_eq!(output, \"palo\");\n    }\n\n    #[test]\n    fn test_parse_recommendation_output_1() {\n        let text = \"\\nenter\\npalo\".to_string();\n        let output = parse_output_single(text, SuggestionType::SingleRecommendation).unwrap();\n        assert_eq!(output, \"palo\");\n    }\n\n    #[test]\n    fn test_parse_recommendation_output_2() {\n        let text = \"p\\nenter\\npalo\".to_string();\n        let output = parse_output_single(text, SuggestionType::SingleRecommendation).unwrap();\n        assert_eq!(output, \"palo\");\n    }\n\n    #[test]\n    fn test_parse_recommendation_output_3() {\n        let text = \"peter\\nenter\\n\".to_string();\n        let output = parse_output_single(text, SuggestionType::SingleRecommendation).unwrap();\n        assert_eq!(output, \"peter\");\n    }\n\n    #[test]\n    fn test_parse_output3() {\n        let text = \"p\\ntab\\npalo\".to_string();\n        let output = parse_output_single(text, SuggestionType::SingleRecommendation).unwrap();\n        assert_eq!(output, \"p\");\n    }\n\n    #[test]\n    fn test_parse_snippet_request() {\n        let text = \"enter\\nssh                     ⠀login to a server and forward to ssh key (d…  ⠀ssh -A <user>@<server>  ⠀ssh  ⠀login to a server and forward to ssh key (dangerous but useful for bastion hosts)  ⠀ssh -A <user>@<server>  ⠀\\n\".to_string();\n        let output = parse_output_single(text, SuggestionType::SnippetSelection).unwrap();\n        assert_eq!(output,     \"enter\\nssh                     ⠀login to a server and forward to ssh key (d…  ⠀ssh -A <user>@<server>  ⠀ssh  ⠀login to a server and forward to ssh key (dangerous but useful for bastion hosts)  ⠀ssh -A <user>@<server>  ⠀\");\n    }\n}\n"
  },
  {
    "path": "src/finder/structures.rs",
    "content": "use crate::filesystem;\nuse crate::prelude::*;\n\n#[derive(Debug, PartialEq, Clone)]\npub struct Opts {\n    pub query: Option<String>,\n    pub filter: Option<String>,\n    pub prompt: Option<String>,\n    pub preview: Option<String>,\n    pub preview_window: Option<String>,\n    pub overrides: Option<String>,\n    pub header_lines: u8,\n    pub header: Option<String>,\n    pub suggestion_type: SuggestionType,\n    pub delimiter: Option<String>,\n    pub column: Option<u8>,\n    pub map: Option<String>,\n    pub prevent_select1: bool,\n    pub show_all_columns: bool,\n}\n\nimpl Default for Opts {\n    fn default() -> Self {\n        Self {\n            query: None,\n            filter: None,\n            preview: None,\n            preview_window: None,\n            overrides: None,\n            header_lines: 0,\n            header: None,\n            prompt: None,\n            suggestion_type: SuggestionType::SingleSelection,\n            column: None,\n            delimiter: None,\n            map: None,\n            prevent_select1: true,\n            show_all_columns: false,\n        }\n    }\n}\n\nimpl Opts {\n    pub fn snippet_default() -> Self {\n        Self {\n            suggestion_type: SuggestionType::SnippetSelection,\n            overrides: CONFIG.fzf_overrides(),\n            preview: Some(format!(\"{} preview {{}}\", filesystem::exe_string())),\n            prevent_select1: !CONFIG.best_match(),\n            query: if CONFIG.best_match() {\n                None\n            } else {\n                CONFIG.get_query()\n            },\n            filter: if CONFIG.best_match() {\n                CONFIG.get_query()\n            } else {\n                None\n            },\n            ..Default::default()\n        }\n    }\n\n    pub fn var_default() -> Self {\n        Self {\n            overrides: CONFIG.fzf_overrides_var(),\n            suggestion_type: SuggestionType::SingleRecommendation,\n            prevent_select1: false,\n            delimiter: CONFIG.delimiter_var(),\n            ..Default::default()\n        }\n    }\n}\n\n#[derive(Clone, Copy, Debug, PartialEq)]\npub enum SuggestionType {\n    /// finder will not print any suggestions\n    Disabled,\n    /// finder will only select one of the suggestions\n    SingleSelection,\n    /// finder will select multiple suggestions\n    MultipleSelections,\n    /// finder will select one of the suggestions or use the query\n    SingleRecommendation,\n    /// initial snippet selection\n    SnippetSelection,\n}\n"
  },
  {
    "path": "src/lib.rs",
    "content": "#[macro_use]\nextern crate lazy_static;\n\nmod clients;\nmod commands;\nmod common;\nmod config;\nmod deser;\nmod env_var;\nmod filesystem;\nmod finder;\nmod parser;\npub mod prelude;\nmod structures;\nmod welcome;\n\nmod libs {\n    pub mod dns_common;\n}\n\npub use {commands::handle, filesystem::default_config_pathbuf};\n"
  },
  {
    "path": "src/libs/dns_common/component.rs",
    "content": "use crate::prelude::*;\n\npub trait Component: Any + AsAny + Send + Sync {}\n\npub trait AsAny: Any {\n    fn as_any(&self) -> &dyn Any;\n    fn as_mut_any(&mut self) -> &mut dyn Any;\n}\n"
  },
  {
    "path": "src/libs/dns_common/mod.rs",
    "content": "pub mod component;\nmod tracing;\n"
  },
  {
    "path": "src/libs/dns_common/tracing.rs",
    "content": "use crate::prelude::*;\n\n#[derive(Deserialize, Serialize, Debug, Clone)]\n#[serde(deny_unknown_fields)]\npub struct TracingConfig {\n    pub time: bool,\n    pub level: String,\n}\n"
  },
  {
    "path": "src/parser.rs",
    "content": "use crate::common::fs;\nuse crate::deser;\nuse crate::finder::structures::{Opts as FinderOpts, SuggestionType};\nuse crate::prelude::*;\nuse crate::structures::cheat::VariableMap;\nuse crate::structures::item::Item;\nuse std::io::Write;\n\nlazy_static! {\n    pub static ref VAR_LINE_REGEX: Regex = Regex::new(r\"^\\$\\s*([^:]+):(.*)\").expect(\"Invalid regex\");\n}\n\nfn parse_opts(text: &str) -> Result<FinderOpts> {\n    let mut multi = false;\n    let mut prevent_extra = false;\n\n    let mut opts = FinderOpts::var_default();\n\n    let parts = shellwords::split(text).map_err(|_| anyhow!(\"Given options are missing a closing quote\"))?;\n\n    parts\n        .into_iter()\n        .filter(|part| {\n            // We'll take parts in pairs of 2: (argument, value). Flags don't have a value tho, so we filter and handle them beforehand.\n            match part.as_str() {\n                \"--multi\" => {\n                    multi = true;\n                    false\n                }\n                \"--prevent-extra\" => {\n                    prevent_extra = true;\n                    false\n                }\n                \"--expand\" => {\n                    opts.map = Some(format!(\"{} fn map::expand\", fs::exe_string()));\n                    false\n                }\n                _ => true,\n            }\n        })\n        .collect::<Vec<_>>()\n        .chunks(2)\n        .try_for_each(|flag_and_value| {\n            if let [flag, value] = flag_and_value {\n                match flag.as_str() {\n                    \"--headers\" | \"--header-lines\" => {\n                        opts.header_lines = value\n                            .parse::<u8>()\n                            .context(\"Value for `--headers` is invalid u8\")?\n                    }\n                    \"--column\" => {\n                        opts.column = Some(\n                            value\n                                .parse::<u8>()\n                                .context(\"Value for `--column` is invalid u8\")?,\n                        )\n                    }\n                    \"--map\" => opts.map = Some(value.to_string()),\n                    \"--delimiter\" => opts.delimiter = Some(value.to_string()),\n                    \"--query\" => opts.query = Some(value.to_string()),\n                    \"--filter\" => opts.filter = Some(value.to_string()),\n                    \"--preview\" => opts.preview = Some(value.to_string()),\n                    \"--preview-window\" => opts.preview_window = Some(value.to_string()),\n                    \"--header\" => opts.header = Some(value.to_string()),\n                    \"--fzf-overrides\" => opts.overrides = Some(value.to_string()),\n                    _ => (),\n                }\n                Ok(())\n            } else if let [flag] = flag_and_value {\n                Err(anyhow!(\"No value provided for the flag `{}`\", flag))\n            } else {\n                unreachable!() // Chunking by 2 allows only for tuples of 1 or 2 items...\n            }\n        })\n        .context(\"Failed to parse finder options\")?;\n\n    let suggestion_type = match (multi, prevent_extra) {\n        (true, _) => SuggestionType::MultipleSelections, // multi wins over prevent-extra\n        (false, false) => SuggestionType::SingleRecommendation,\n        (false, true) => SuggestionType::SingleSelection,\n    };\n    opts.suggestion_type = suggestion_type;\n\n    Ok(opts)\n}\n\nfn parse_variable_line(line: &str) -> Result<(&str, &str, Option<FinderOpts>)> {\n    let caps = VAR_LINE_REGEX\n        .captures(line)\n        .ok_or_else(|| anyhow!(\"No variables, command, and options found in the line `{}`\", line))?;\n    let variable = caps\n        .get(1)\n        .ok_or_else(|| anyhow!(\"No variable captured in the line `{}`\", line))?\n        .as_str()\n        .trim();\n    let mut command_plus_opts = caps\n        .get(2)\n        .ok_or_else(|| anyhow!(\"No command and options captured in the line `{}`\", line))?\n        .as_str()\n        .split(\"---\");\n    let command = command_plus_opts\n        .next()\n        .ok_or_else(|| anyhow!(\"No command captured in the line `{}`\", line))?;\n    let command_options = command_plus_opts.next().map(parse_opts).transpose()?;\n    Ok((variable, command, command_options))\n}\n\nfn without_prefix(line: &str) -> String {\n    if line.len() > 2 {\n        String::from(line[2..].trim())\n    } else {\n        String::from(\"\")\n    }\n}\n\n#[derive(Clone, Default)]\npub struct FilterOpts {\n    pub allowlist: Vec<String>,\n    pub denylist: Vec<String>,\n    pub hash: Option<u64>,\n}\n\npub struct Parser<'a> {\n    pub variables: VariableMap,\n    visited_lines: HashSet<u64>,\n    filter: FilterOpts,\n    writer: &'a mut dyn Write,\n    write_fn: fn(&Item) -> String,\n}\n\nfn without_first(string: &str) -> String {\n    string\n        .char_indices()\n        .next()\n        .and_then(|(i, _)| string.get(i + 1..))\n        .expect(\"Should have at least one char\")\n        .to_string()\n}\n\nfn gen_lists(tag_rules: &str) -> FilterOpts {\n    let words: Vec<_> = tag_rules.split(',').collect();\n\n    let allowlist = words\n        .iter()\n        .filter(|w| !w.starts_with('!'))\n        .map(|w| w.to_string())\n        .collect();\n\n    let denylist = words\n        .iter()\n        .filter(|w| w.starts_with('!'))\n        .map(|w| without_first(w))\n        .collect();\n\n    FilterOpts {\n        allowlist,\n        denylist,\n        ..Default::default()\n    }\n}\n\nimpl<'a> Parser<'a> {\n    pub fn new(writer: &'a mut dyn Write, is_terminal: bool) -> Self {\n        let write_fn = if is_terminal {\n            deser::terminal::write\n        } else {\n            deser::raycast::write\n        };\n\n        let filter = match CONFIG.tag_rules() {\n            Some(tr) => gen_lists(&tr),\n            None => Default::default(),\n        };\n\n        Self {\n            variables: Default::default(),\n            visited_lines: Default::default(),\n            filter,\n            write_fn,\n            writer,\n        }\n    }\n\n    pub fn set_hash(&mut self, hash: u64) {\n        self.filter.hash = Some(hash)\n    }\n\n    fn write_cmd(&mut self, item: &Item) -> Result<()> {\n        if item.comment.is_empty() || item.snippet.trim().is_empty() {\n            return Ok(());\n        }\n\n        let hash = item.hash();\n        if self.visited_lines.contains(&hash) {\n            return Ok(());\n        }\n        self.visited_lines.insert(hash);\n\n        if !self.filter.denylist.is_empty() {\n            for v in &self.filter.denylist {\n                if item.tags.contains(v) {\n                    return Ok(());\n                }\n            }\n        }\n\n        if !self.filter.allowlist.is_empty() {\n            let mut should_allow = false;\n            for v in &self.filter.allowlist {\n                if item.tags.contains(v) {\n                    should_allow = true;\n                    break;\n                }\n            }\n            if !should_allow {\n                return Ok(());\n            }\n        }\n\n        if let Some(h) = self.filter.hash {\n            if h != hash {\n                return Ok(());\n            }\n        }\n\n        let write_fn = self.write_fn;\n\n        self.writer\n            .write_all(write_fn(item).as_bytes())\n            .context(\"Failed to write command to finder's stdin\")\n    }\n\n    pub fn read_lines(\n        &mut self,\n        lines: impl Iterator<Item = Result<String>>,\n        id: &str,\n        file_index: Option<usize>,\n    ) -> Result<()> {\n        let mut item = Item::new(file_index);\n\n        let mut should_break = false;\n\n        let mut variable_cmd = String::from(\"\");\n\n        let mut inside_snippet: bool = false;\n\n        for (line_nr, line_result) in lines.enumerate() {\n            let line = line_result\n                .with_context(|| format!(\"Failed to read line number {line_nr} in cheatsheet `{id}`\"))?;\n\n            if should_break {\n                break;\n            }\n\n            // duplicate\n            // if !item.tags.is_empty() && !item.comment.is_empty() {}\n\n            // blank\n            if line.is_empty() {\n                if !item.snippet.is_empty() {\n                    item.snippet.push_str(deser::LINE_SEPARATOR);\n                }\n            }\n            // tag\n            else if line.starts_with('%') {\n                should_break = self.write_cmd(&item).is_err();\n                item.snippet = String::from(\"\");\n                item.tags = without_prefix(&line);\n            }\n            // dependency\n            else if line.starts_with('@') {\n                let tags_dependency = without_prefix(&line);\n                self.variables.insert_dependency(&item.tags, &tags_dependency);\n            }\n            // raycast icon\n            else if let Some(icon) = line.strip_prefix(\"; raycast.icon:\") {\n                item.icon = Some(icon.trim().into());\n            }\n            // metacomment\n            else if line.starts_with(';') {\n            }\n            // comment\n            else if line.starts_with('#') {\n                should_break = self.write_cmd(&item).is_err();\n                item.snippet = String::from(\"\");\n                item.comment = without_prefix(&line);\n            }\n            // variable\n            else if !variable_cmd.is_empty()\n                || (line.starts_with('$') && line.contains(':')) && !inside_snippet\n            {\n                should_break = self.write_cmd(&item).is_err();\n\n                item.snippet = String::from(\"\");\n\n                variable_cmd.push_str(line.trim_end_matches('\\\\'));\n\n                if !line.ends_with('\\\\') {\n                    let full_variable_cmd = variable_cmd.clone();\n                    let (variable, command, opts) =\n                        parse_variable_line(&full_variable_cmd).with_context(|| {\n                            format!(\n                                \"Failed to parse variable line. See line number {} in cheatsheet `{}`\",\n                                line_nr + 1,\n                                id\n                            )\n                        })?;\n                    variable_cmd = String::from(\"\");\n                    self.variables\n                        .insert_suggestion(&item.tags, variable, (String::from(command), opts));\n                }\n            }\n            // markdown snippet\n            else if line.starts_with(\"```\") {\n                inside_snippet = !inside_snippet;\n            }\n            // snippet\n            else {\n                if !item.snippet.is_empty() {\n                    item.snippet.push_str(deser::LINE_SEPARATOR);\n                }\n                item.snippet.push_str(&line);\n            }\n        }\n\n        if !should_break {\n            let _ = self.write_cmd(&item);\n        }\n\n        Ok(())\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_parse_variable_line() {\n        let (variable, command, command_options) =\n            parse_variable_line(\"$ user : echo -e \\\"$(whoami)\\\\nroot\\\" --- --prevent-extra\").unwrap();\n        assert_eq!(command, \" echo -e \\\"$(whoami)\\\\nroot\\\" \");\n        assert_eq!(variable, \"user\");\n        let opts = command_options.unwrap();\n        assert_eq!(opts.header_lines, 0);\n        assert_eq!(opts.column, None);\n        assert_eq!(opts.delimiter, None);\n        assert_eq!(opts.suggestion_type, SuggestionType::SingleSelection);\n    }\n}\n"
  },
  {
    "path": "src/prelude.rs",
    "content": "pub use crate::common::deps::HasDeps;\npub use crate::common::fs::ToStringExt;\npub use crate::config::CONFIG; // TODO\npub use crate::libs::dns_common;\npub use anyhow::{anyhow, Context, Error, Result};\npub use regex::Regex;\npub use serde::de::Deserializer;\npub use serde::ser::Serializer;\npub use serde::{Deserialize, Serialize};\npub use std::any::{Any, TypeId};\npub use std::collections::{HashMap, HashSet};\npub use std::convert::{TryFrom, TryInto};\npub use std::fs::File;\npub use std::io::{BufRead, BufReader};\npub use std::path::{Path, PathBuf};\npub use std::process::Stdio;\npub use std::str::FromStr;\npub use std::sync::{Arc, Mutex, RwLock};\npub use tracing::{self, debug, error, event, info, instrument, span, subscriber, trace, warn};\n\npub trait Component: Any + AsAny + Send + Sync {}\n\npub trait AsAny: Any {\n    fn as_any(&self) -> &dyn Any;\n    fn as_mut_any(&mut self) -> &mut dyn Any;\n}\n\nimpl<T> AsAny for T\nwhere\n    T: Any,\n{\n    fn as_any(&self) -> &dyn Any {\n        self\n    }\n\n    fn as_mut_any(&mut self) -> &mut dyn Any {\n        self\n    }\n}\n\npub trait Runnable {\n    fn run(&self) -> Result<()>;\n}\n"
  },
  {
    "path": "src/structures/cheat.rs",
    "content": "use crate::common::hash::fnv;\nuse crate::finder::structures::Opts;\nuse crate::prelude::*;\n\npub type Suggestion = (String, Option<Opts>);\n\n#[derive(Clone, Default)]\npub struct VariableMap {\n    variables: HashMap<u64, HashMap<String, Suggestion>>,\n    dependencies: HashMap<u64, Vec<u64>>,\n}\n\nimpl VariableMap {\n    pub fn insert_dependency(&mut self, tags: &str, tags_dependency: &str) {\n        let k = fnv(&tags);\n        if let Some(v) = self.dependencies.get_mut(&k) {\n            v.push(fnv(&tags_dependency));\n        } else {\n            let v: Vec<u64> = vec![fnv(&tags_dependency)];\n            self.dependencies.insert(k, v);\n        }\n    }\n\n    pub fn insert_suggestion(&mut self, tags: &str, variable: &str, value: Suggestion) {\n        let k1 = fnv(&tags);\n        let k2 = String::from(variable);\n        if let Some(m) = self.variables.get_mut(&k1) {\n            m.insert(k2, value);\n        } else {\n            let mut m = HashMap::new();\n            m.insert(k2, value);\n            self.variables.insert(k1, m);\n        }\n    }\n\n    pub fn get_suggestion(&self, tags: &str, variable: &str) -> Option<&Suggestion> {\n        let k = fnv(&tags);\n\n        if let Some(vm) = self.variables.get(&k) {\n            let res = vm.get(variable);\n            if res.is_some() {\n                return res;\n            }\n        }\n\n        if let Some(dependency_keys) = self.dependencies.get(&k) {\n            for dependency_key in dependency_keys {\n                if let Some(vm) = self.variables.get(dependency_key) {\n                    let res = vm.get(variable);\n                    if res.is_some() {\n                        return res;\n                    }\n                }\n            }\n        }\n\n        None\n    }\n}\n"
  },
  {
    "path": "src/structures/fetcher.rs",
    "content": "use crate::parser::Parser;\nuse crate::prelude::*;\n\npub trait Fetcher {\n    fn fetch(&self, parser: &mut Parser) -> Result<bool>;\n\n    fn files(&self) -> Vec<String> {\n        vec![]\n    }\n}\n\npub struct StaticFetcher {\n    lines: Vec<String>,\n}\n\nimpl StaticFetcher {\n    pub fn new(lines: Vec<String>) -> Self {\n        Self { lines }\n    }\n}\n\nimpl Fetcher for StaticFetcher {\n    fn fetch(&self, parser: &mut Parser) -> Result<bool> {\n        parser.read_lines(self.lines.clone().into_iter().map(Ok), \"static\", None)?;\n        Ok(true)\n    }\n}\n"
  },
  {
    "path": "src/structures/item.rs",
    "content": "use crate::common::hash::fnv;\n\n#[derive(Default, Debug)]\npub struct Item {\n    pub tags: String,\n    pub comment: String,\n    pub snippet: String,\n    pub file_index: Option<usize>,\n    pub icon: Option<String>,\n}\n\nimpl Item {\n    pub fn new(file_index: Option<usize>) -> Self {\n        Self {\n            file_index,\n            ..Default::default()\n        }\n    }\n\n    pub fn hash(&self) -> u64 {\n        fnv(&format!(\n            \"{}{}{}\",\n            &self.tags.trim(),\n            &self.comment.trim(),\n            &self.snippet.trim()\n        ))\n    }\n}\n"
  },
  {
    "path": "src/structures/mod.rs",
    "content": "pub mod cheat;\npub mod fetcher;\npub mod item;\n"
  },
  {
    "path": "src/welcome.rs",
    "content": "use crate::parser::Parser;\nuse crate::prelude::*;\nuse crate::structures::fetcher;\n\npub fn populate_cheatsheet(parser: &mut Parser) -> Result<()> {\n    let cheatsheet = include_str!(\"../docs/examples/cheatsheet/navi.cheat\");\n    let lines = cheatsheet.split('\\n').map(|s| Ok(s.to_string()));\n\n    parser.read_lines(lines, \"welcome\", None)?;\n\n    Ok(())\n}\n\npub struct Fetcher {}\n\nimpl Fetcher {\n    pub fn new() -> Self {\n        Self {}\n    }\n}\n\nimpl fetcher::Fetcher for Fetcher {\n    fn fetch(&self, parser: &mut Parser) -> Result<bool> {\n        populate_cheatsheet(parser)?;\n        Ok(true)\n    }\n}\n"
  },
  {
    "path": "tests/cheats/more_cases.cheat",
    "content": "; author: CI/CD\n\n% test, ci/cd\n\n# escape code + subshell\necho -ne \"\\033]0;$(hostname)\\007\"\n\n# env var\necho \"$HOME\"\n\n# multi + column\nmyfn() {\n    for i in $@; do\n        echo -e \"arg: $i\\n\"\n    done\n}\nfolders=($(echo \"<multi_col>\"))\nmyfn \"${folders[@]}\"\n\n# second column: default delimiter\necho \"<table_elem> is cool\"\n\n# second column: custom delimiter\necho \"<table_elem2> is cool\"\n\n# return multiple results: single words\necho \"I like these languages: \"$(printf '%s' \"<langs>\" | tr '\\n' ',' | sed 's/,/, /g')\"\"\n\n# return multiple results: multiple words\necho \"I like these examples: \"$(printf '%s' \"<examples>\" | sed 's/^..*$/\"&\"/' | awk 1 ORS=', ' | sed 's/, $//')\"\"\n\n# multiple replacements -> \"foo\"\necho \"<x> <y> <x> <z>\"\n\n# with preview\ncat \"<file>\"\n\n# with map\necho \"<mapped>\"\n\n# empty\necho \"http://google.com?q=<query>\"\n\n# fzf\nls / | fzf\n\n# \necho description space\n\n#\necho description blank\n\n# x\necho description one character\n\n# map can be used to expand into multiple arguments\nfor l in <phrases>; do echo \"line: $l\"; done\n\n# x\necho <with_overrides>\n\n# Concatenate pdf files\nfiles=($(echo \"<files>\"))\necho pdftk \"${files[@]:-}\" cat output <pdf_output>\n\n$ files: echo 'file1.pdf file2.pdf file3.pdf' | tr ' ' '\\n' --- --multi --fzf-overrides '--tac'\n$ x: echo '1 2 3' | tr ' ' '\\n'\n$ y: echo 'a b c' | tr ' ' '\\n'\n$ z: echo 'foo bar' | tr ' ' '\\n'\n$ table_elem: echo -e '0  rust      rust-lang.org\\n1  clojure   clojure.org' --- --column 2\n$ table_elem2: echo -e '0;rust;rust-lang.org\\n1;clojure;clojure.org' --- --column 2 --delimiter ';'\n$ multi_col: ls -la | awk '{print $1, $9}' --- --column 2 --delimiter '\\s' --multi\n$ langs: echo 'clojure rust javascript' | tr ' ' '\\n' --- --multi\n$ mapped: echo 'true false' | tr ' ' '\\n' --- --map \"grep -q t && echo 1 || echo 0\"\n$ examples: echo -e 'foo bar\\nlorem ipsum\\ndolor sit' --- --multi\n$ multiword: echo -e 'foo bar\\nlorem ipsum\\ndolor sit\\nbaz'i\n$ file: ls . --- --preview 'cat {}' --preview-window 'right:50%'\n$ phrases: echo -e \"foo bar\\nlorem ipsum\\ndolor sit\" --- --multi --map \"navi fn map::expand\"\n$ with_overrides: echo -e \"foo bar\\nlorem ipsum\\ndolor sit\" --- --fzf-overrides \"--margin=15% --bind=ctrl-u:replace-query\"\n\n# this should be displayed\necho hi\n\n%\n\n# Without tag\necho hi 1 2 3"
  },
  {
    "path": "tests/cheats/ssh.cheat",
    "content": "% ssh\n\n# login to a server with a key and port\nssh -i <sshkey> -p <port> <user>@<server>\n\n$ user : echo -e \"$(whoami)\\nroot\" --- --prevent-extra\n"
  },
  {
    "path": "tests/config.yaml",
    "content": "style:\n  tag:\n    color: cyan\n    width_percentage: 26\n    min_width: 20\n  comment:\n    color: yellow\n    width_percentage: 42\n    min_width: 45\n  snippet:\n    color: white\n\nfinder:\n  command: fzf\n\nshell:\n  finder_command: bash\n  command: env BASH_ENV=\"${NAVI_HOME}/tests/helpers.sh\" bash --norc --noprofile\n"
  },
  {
    "path": "tests/core.bash",
    "content": "#!/usr/bin/env bash\n# vim: filetype=sh\n\nsource \"${NAVI_HOME}/scripts/install\"\n\nNEWLINE_CHAR=\"\\036\"\n\nPASSED=0\nFAILED=0\nSKIPPED=0\nSUITE=\"\"\n\ntest::set_suite() {\n   SUITE=\"$*\"\n}\n\ntest::success() {\n   PASSED=$((PASSED+1))\n   log::success \"Test passed!\"\n}\n\ntest::fail() {\n   FAILED=$((FAILED+1))\n   log::error \"Test failed...\"\n   return\n}\n\ntest::skip() {\n   echo\n   log::note \"${SUITE:-unknown} - ${1:-unknown}\"\n   SKIPPED=$((SKIPPED+1))\n   log::warning \"Test skipped...\"\n   return\n}\n\ntest::run() {\n   echo\n   log::note \"${SUITE:-unknown} - ${1:-unknown}\"\n   shift\n   \"$@\" && test::success || test::fail\n}\n\ntest::_escape() {\n   tr '\\n' \"$NEWLINE_CHAR\" | sed -E \"s/[\\s$(printf \"$NEWLINE_CHAR\") ]+$//g\"\n}\n\ntest::equals() {\n   local -r actual=\"$(cat)\"\n   local -r expected=\"${1:-}\"\n\n   local -r actual2=\"$(echo \"$actual\" | test::_escape)\"\n   local -r expected2=\"$(echo \"$expected\" | test::_escape)\"\n\n   if [[ \"$actual2\" != \"$expected2\" ]]; then\n      log::error \"Expected '${expected}' but got '${actual}'\"\n      return 2\n   fi\n}\n\ntest::contains() {\n   local -r haystack=\"$(cat)\"\n   local -r needle=\"${1:-}\"\n\n   local -r haystack2=\"$(echo \"$haystack\" | test::_escape)\"\n   local -r needle2=\"$(echo \"$needle\" | test::_escape)\"\n\n   if [[ \"$haystack2\" != *\"$needle2\"* ]]; then\n      log::error \"Expected '${haystack}' to include '${needle2}'\"\n      return 2\n   fi\n}\n\ntest::finish() {\n   echo\n   if [ $SKIPPED -gt 0 ]; then\n      log::warning \"${SKIPPED} tests skipped!\"\n   fi\n   if [ $FAILED -gt 0 ]; then\n      log::error \"${PASSED} tests passed but ${FAILED} failed... :(\"\n      exit \"${FAILED}\"\n   else\n      log::success \"All ${PASSED} tests passed! :)\"\n      exit 0\n   fi\n}\n"
  },
  {
    "path": "tests/helpers.sh",
    "content": "#!/usr/local/bin/env bash\n\nmyhelperfn() {\n   echo \"inside helper: $*\"\n}\n"
  },
  {
    "path": "tests/no_prompt_cheats/cases.cheat",
    "content": "; author: CI/CD\n\n% test, first\n\n# trivial case -> \"foo\"\necho \"foo\"\n\n# map with underscores -> \"_foo_\"\necho \"<map1>\"\n\n# expand -> \"foo\"\necho \"<expand1>\"\n\n# duplicated lines -> \"foo\\nlorem ipsum\\nlorem ipsum\\nbaz\"\necho foo\necho lorem ipsum\necho lorem ipsum\necho baz\n\n# empty line -> \"foo\\n\\n\\nbar\"\necho \"$(cat <<EOF\nfoo\n\n\nbar\nEOF\n)\"\n\n# sed with replacement -> \"172.17.0.2\"\necho \"8.8.8.8 via 172.17.0.1 dev eth0  src 172.17.0.2\" | sed -E 's/.*src ([0-9.]+).*/\\1/p' | head -n1\n\n# 2nd column with default delimiter -> \"rust is cool\"\necho \"<language> is cool\"\n\n# 2nd column with custom delimiter -> \"clojure is cool\"\necho \"<language2> is cool\"\n\n# multiple words -> \"lorem foo bar ipsum\"\necho \"lorem <multiword> ipsum\"\n\n# variable dependency, full -> \"2 12 a 2\"\necho \"<x> <x2> <y> <x>\"\n\n; # variable dependency, we can ignore intermediate values -> \"foo 12\"\n; printf \"foo \"; : <x>; echo \"<x2>\"\n\n# nested unused value -> \"path: /my/pictures\"\necho \"path: <pictures_folder>\"\n\n# multiline command: no backslash -> \"foo\\nbar\"\necho \"foo\"\necho \"bar\"\n\n# multiline command: with backslash -> \"lorem ipsum\\nno match\"\necho 'lorem ipsum'\necho \"foo\" \\\n    | grep -q \"bar\" \\\n    && echo \"match\" \\\n    || echo \"no match\"\n\n# multiline variable -> \"foo bar\"\necho \"<multilinevar>\"\n\n# helper -> \"inside helper: 42\"\nmyhelperfn 42\n\n$ x: echo '2'\n$ x2: echo \"$((x+10))\"\n$ y: echo 'a'\n$ language: echo '0  rust      rust-lang.org' --- --column 2\n$ language2: echo '1;clojure;clojure.org' --- --column 2 --delimiter ';'\n$ multiword: echo 'foo bar'\n$ pictures_folder: echo \"/my/pictures\"\n$ map1: echo \"foo\" --- --map 'echo _$(cat)_'\n$ multilinevar: echo \"xoo yar\" \\\n   | tr 'x' 'f' \\\n   | tr 'y' 'b'\n$ expand1: echo \"foo\" --- --expand\n\n\n# this should be displayed -> \"hi\"\necho hi\n\n\n% test, second\n\n@ test, first\n@ test, third\n\n# nested used value -> \"path: /my/pictures/wallpapers\"\necho \"path: <wallpaper_folder>\"\n\n# same command as before -> \"12\"\n: <x>; echo \"<x2>\"\n\n# the order isn't relevant -> \"br\"\necho \"<country>\"\n\n$ wallpaper_folder: echo \"<pictures_folder>/wallpapers\"\n\n\n% test, third\n\n; this cheathsheet doesn't have any commands\n$ country: echo \"br\""
  },
  {
    "path": "tests/no_prompt_cheats/one.cheat",
    "content": "; author: CI/CD\n\n% test, first\n\n# trivial case -> \"foo\"\necho \"foo\"\n\n"
  },
  {
    "path": "tests/run",
    "content": "#!/usr/bin/env bash\nset -euo pipefail\n\nexport NAVI_HOME=\"$(cd \"$(dirname \"$0\")/..\" && pwd)\"\nsource \"${NAVI_HOME}/tests/core.bash\"\n\nexport TEST_CHEAT_PATH=\"${NAVI_HOME}/tests/no_prompt_cheats\"\nexport NAVI_EXE=\"${NAVI_HOME}/target/debug/navi\"\n\nif ! command_exists navi; then\n   navi() {\n      \"$NAVI_EXE\" \"$@\"\n   }\n   export -f navi\nfi\n\n_navi() {\n   stty sane || true\n   local path=\"${NAVI_TEST_PATH:-$TEST_CHEAT_PATH}\"\n   path=\"${path//$HOME/~}\"\n   export NAVI_ENV_VAR_PATH=\"$path\"\n   RUST_BACKTRACE=1 NAVI_PATH='$NAVI_ENV_VAR_PATH' NAVI_CONFIG=\"${NAVI_HOME}/tests/config.yaml\" \"$NAVI_EXE\" \"$@\"\n}\n\n_navi_cases() {\n   local -r filter=\"${1::-2}\"\n   _navi --query \"$filter\" --best-match\n}\n\n_navi_cases_test() {\n   _navi_cases \"$1\" \\\n      | test::equals \"$2\"\n}\n\n_get_all_tests() {\n   cat \"${TEST_CHEAT_PATH}/cases.cheat\" \\\n      | grep '^#' \\\n      | grep ' ->' \\\n      | sed 's/\\\\n/'\"$(printf \"$NEWLINE_CHAR\")\"'/g' \\\n      | sed -E 's/# (.*) -> \"(.*)\"/\\1|\\2/g'\n}\n\n_get_tests() {\n   local -r filter=\"$1\"\n\n   if [ -n \"$filter\" ]; then\n      _get_all_tests \\\n         | grep \"$filter\"\n   else\n      _get_all_tests\n   fi\n}\n\n_navi_tldr() {\n   _navi --tldr docker --query ps --print --best-match \\\n      | test::contains \"docker ps\"\n}\n\n_navi_cheatsh() {\n   _navi --cheatsh docker --query remove --print --best-match \\\n      | test::contains \"docker container prune\"\n}\n\n_navi_widget() {\n   local -r out=\"$(_navi widget \"$1\")\"\n   if ! echo \"$out\" | grep -q \"navi \"; then\n      echo \"$out\"\n      return 1\n   fi\n}\n\n_navi_cheatspath() {\n   _navi info cheats-path \\\n      | grep -q \"/cheats\"\n}\n\n_kill_tmux() {\n   pkill -f tmux 2>/dev/null || true\n}\n\n_assert_tmux() {\n   local -r log_file=\"$1\"\n   local -r sessions=\"$(tmux list-sessions)\"\n   if [ -z \"$sessions\" ]; then\n      _kill_tmux\n      cat \"$log_file\"\n      return 1\n   fi\n}\n\n_integration() {\n   _kill_tmux\n   local -r log_file=\"${NAVI_HOME}/target/ci.log\"\n   local -r cheats_path=\"$($NAVI_EXE info cheats-path)\"\n   rm -rf \"$cheats_path\" 2>/dev/null || true\n   mkdir -p \"$cheats_path\" 2>/dev/null || true\n   local -r bak_cheats_path=\"$(mktemp -d \"${cheats_path}_XXXXX\")\"\n   rm \"$log_file\" 2>/dev/null || true\n   mv \"$cheats_path\" \"$bak_cheats_path\" 2>/dev/null || true\n\n   log::note \"Starting sessions...\"\n   tmux new-session -d -s ci \"export NAVI_TEST_PATH='${cheats_path}'; ${NAVI_HOME}/tests/run _navi |& tee '${log_file}'\"\n   sleep 5\n   _assert_tmux \"$log_file\"\n\n   log::note \"Downloading default cheatsheets...\"\n   tmux send-key -t ci \"download default\"; tmux send-key -t ci \"Enter\"\n   sleep 1\n   _assert_tmux \"$log_file\"\n\n   log::note \"Confirming import...\"\n   tmux send-key -t ci \"y\"\n   sleep 1\n   tmux send-key -t ci \"Enter\"\n   sleep 6\n   _assert_tmux \"$log_file\"\n\n   log::note \"Running snippet...\"\n   tmux send-key -t ci \"pwd\"\n   sleep 1\n   tmux send-key -t ci \"Enter\"\n\n   log::note \"Checking paths...\"\n   sleep 2\n   local -r downloaded_path=\"$(cat \"$log_file\" | grep 'They are now located at' | sed 's/They are now located at //')\"\n   ls \"$downloaded_path\" | grep -q '^pkg_mgr__brew.cheat$'\n}\n\nif ! command_exists fzf; then\n   export PATH=\"$PATH:$HOME/.fzf/bin\"\nfi\n\ncd \"$NAVI_HOME\"\n\nfilter=\"${1:-}\"\n\n# TODO: remove this\nif [[ $filter == \"_navi\" ]]; then\n   shift\n   _navi \"$@\"\n   exit 0\nfi\n\ntest::set_suite \"cases\"\nifs=\"$IFS\"\nIFS=$'\\n'\nfor i in $(_get_tests \"$filter\"); do\n   IFS=\"$ifs\"\n   query=\"$(echo \"$i\" | cut -d'|' -f1)\"\n   expected=\"$(echo \"$i\" | tr \"$NEWLINE_CHAR\" '\\n' | cut -d'|' -f2)\"\n   test::run \"$query\" _navi_cases_test \"$query\" \"$expected\"\ndone\n\ntest::set_suite \"info\"\ntest::run \"cheats_path\" _navi_cheatspath\n\ntest::set_suite \"widget\"\ntest::run \"bash\" _navi_widget \"bash\"\ntest::run \"zsh\" _navi_widget \"zsh\"\ntest::run \"fish\" _navi_widget \"fish\"\ntest::run \"elvish\" _navi_widget \"elvish\"\ntest::run \"nu\" _navi_widget \"nushell\"\n\ntest::set_suite \"3rd party\"\ntest::run \"tldr\" _navi_tldr\ntest::run \"cheatsh\" _navi_cheatsh\n\ntest::set_suite \"integration\"\ntest::run \"welcome->pwd\" _integration\n\ntest::finish\n"
  },
  {
    "path": "tests/tests.rs",
    "content": "#[cfg(test)]\nmod tests {\n    #[test]\n    fn it_works() {\n        //let _x = navi::handle_config(navi::config_from_iter(\n        //\"navi best trivial\".split(' ').collect(),\n        //));\n        // assert_eq!(x, 3);\n    }\n}\n"
  }
]