Showing preview only (231K chars total). Download the full file or copy to clipboard to get everything.
Repository: denisidoro/navi
Branch: master
Commit: b4ce885253df
Files: 109
Total size: 208.8 KB
Directory structure:
gitextract_8cygam37/
├── .github/
│ ├── CODEOWNERS
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ └── feature_request.md
│ ├── config.yml
│ └── workflows/
│ ├── cd.yml
│ └── ci.yml
├── .gitignore
├── Cargo.toml
├── LICENSE
├── Makefile
├── README.md
├── docs/
│ ├── README.md
│ ├── cheatsheet/
│ │ ├── README.md
│ │ ├── getting-started/
│ │ │ └── README.md
│ │ ├── repositories/
│ │ │ └── README.md
│ │ └── syntax/
│ │ └── README.md
│ ├── configuration/
│ │ └── README.md
│ ├── contributions/
│ │ ├── README.md
│ │ ├── bugs/
│ │ │ └── README.md
│ │ ├── code/
│ │ │ └── README.md
│ │ └── documentation/
│ │ └── README.md
│ ├── deprecated/
│ │ └── Alfred/
│ │ └── README.md
│ ├── examples/
│ │ ├── cheatsheet/
│ │ │ ├── example.cheat
│ │ │ └── navi.cheat
│ │ └── configuration/
│ │ └── config-example.yaml
│ ├── installation/
│ │ └── README.md
│ ├── usage/
│ │ ├── README.md
│ │ ├── commands/
│ │ │ ├── info/
│ │ │ │ └── README.md
│ │ │ └── repo/
│ │ │ └── README.md
│ │ ├── fzf-overrides/
│ │ │ └── README.md
│ │ └── shell-scripting/
│ │ └── README.md
│ └── widgets/
│ ├── README.md
│ └── howto/
│ ├── TMUX.md
│ └── VIM.md
├── rust-toolchain.toml
├── rustfmt.toml
├── scripts/
│ ├── docker
│ ├── dot
│ ├── install
│ ├── make
│ ├── release
│ └── test
├── shell/
│ ├── navi.plugin.bash
│ ├── navi.plugin.elv
│ ├── navi.plugin.fish
│ ├── navi.plugin.nu
│ ├── navi.plugin.ps1
│ └── navi.plugin.zsh
├── src/
│ ├── bin/
│ │ └── main.rs
│ ├── clients/
│ │ ├── cheatsh.rs
│ │ ├── mod.rs
│ │ └── tldr.rs
│ ├── commands/
│ │ ├── core/
│ │ │ ├── actor.rs
│ │ │ └── mod.rs
│ │ ├── func/
│ │ │ ├── map.rs
│ │ │ ├── mod.rs
│ │ │ └── widget.rs
│ │ ├── info.rs
│ │ ├── mod.rs
│ │ ├── preview/
│ │ │ ├── mod.rs
│ │ │ ├── var.rs
│ │ │ └── var_stdin.rs
│ │ ├── repo/
│ │ │ ├── add.rs
│ │ │ ├── browse.rs
│ │ │ └── mod.rs
│ │ ├── shell.rs
│ │ └── temp.rs
│ ├── common/
│ │ ├── clipboard.rs
│ │ ├── deps.rs
│ │ ├── fs.rs
│ │ ├── git.rs
│ │ ├── hash.rs
│ │ ├── mod.rs
│ │ ├── shell.rs
│ │ ├── terminal.rs
│ │ └── url.rs
│ ├── config/
│ │ ├── cli.rs
│ │ ├── env.rs
│ │ ├── mod.rs
│ │ └── yaml.rs
│ ├── deser/
│ │ ├── mod.rs
│ │ ├── raycast.rs
│ │ └── terminal.rs
│ ├── env_var.rs
│ ├── filesystem.rs
│ ├── finder/
│ │ ├── mod.rs
│ │ ├── post.rs
│ │ └── structures.rs
│ ├── lib.rs
│ ├── libs/
│ │ └── dns_common/
│ │ ├── component.rs
│ │ ├── mod.rs
│ │ └── tracing.rs
│ ├── parser.rs
│ ├── prelude.rs
│ ├── structures/
│ │ ├── cheat.rs
│ │ ├── fetcher.rs
│ │ ├── item.rs
│ │ └── mod.rs
│ └── welcome.rs
└── tests/
├── cheats/
│ ├── more_cases.cheat
│ └── ssh.cheat
├── config.yaml
├── core.bash
├── helpers.sh
├── no_prompt_cheats/
│ ├── cases.cheat
│ └── one.cheat
├── run
└── tests.rs
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/CODEOWNERS
================================================
# See: https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
* @denisidoro
.github/* @denisidoro
shell/navi.plugin.ps1 @alexis-opolka
docs/* @alexis-opolka
docs/**/* @alexis-opolka
================================================
FILE: .github/FUNDING.yml
================================================
github: denisidoro
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: ""
labels: bug
assignees: ""
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Versions:**
- OS: [e.g. macOS, WSL ubuntu, ubuntu]
- Shell Version [replace this text with the output of `sh --version`]
**Additional context**
Add any other context about the problem here.
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
title: ""
labels: new feature
assignees: ""
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
================================================
FILE: .github/config.yml
================================================
# Configuration for new-issue-welcome - https://github.com/behaviorbot/new-issue-welcome
# Comment to be posted to on first time issues
newIssueWelcomeComment: >
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! :)
# Configuration for new-pr-welcome - https://github.com/behaviorbot/new-pr-welcome
# Comment to be posted to on PRs from first time contributors in your repository
newPRWelcomeComment: >
Thanks for opening this pull request!
# Configuration for first-pr-merge - https://github.com/behaviorbot/first-pr-merge
# Comment to be posted to on pull requests merged by a first time user
firstPRMergeComment: >
Congrats on merging your first pull request!
================================================
FILE: .github/workflows/cd.yml
================================================
name: Publish
on:
push:
tags:
- "*"
release:
jobs:
binary:
name: Publish ${{ matrix.target }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: macos-latest
target: x86_64-apple-darwin
- os: ubuntu-latest
target: x86_64-unknown-linux-musl
- os: ubuntu-latest
target: x86_64-pc-windows-gnu
- os: ubuntu-latest
target: armv7-unknown-linux-musleabihf
- os: ubuntu-latest
target: armv7-linux-androideabi
- os: ubuntu-latest
target: aarch64-linux-android
- os: ubuntu-latest
target: aarch64-unknown-linux-gnu
- os: macos-latest
target: aarch64-apple-darwin
steps:
### We're checking out the repository at the triggered ref
- uses: actions/checkout@v4
- name: Get the version
id: get_version
run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
- name: Check if release exists
id: check_release
run: |
if gh release view ${{ steps.get_version.outputs.VERSION }} > /dev/null 2>&1; then
echo "RELEASE_EXISTS=true" >> $GITHUB_OUTPUT
else
echo "RELEASE_EXISTS=false" >> $GITHUB_OUTPUT
fi
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Create release
continue-on-error: true
if: steps.check_release.outputs.RELEASE_EXISTS == 'false'
run: |
gh release create ${{ steps.get_version.outputs.VERSION }} \
--title "Release ${{ steps.get_version.outputs.VERSION }}" \
--notes "Release notes for ${{ steps.get_version.outputs.VERSION }}" \
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Build
id: build
run: scripts/release ${{ matrix.target }}
- name: Upload binaries to release
run: |
cd ./target/${{ matrix.target }}/release/
cp navi.${{ steps.build.outputs.EXTENSION }} navi-${{ steps.get_version.outputs.VERSION }}-${{ matrix.target }}.${{ steps.build.outputs.EXTENSION }}
gh release upload ${{ steps.get_version.outputs.VERSION }} navi-${{ steps.get_version.outputs.VERSION }}-${{ matrix.target }}.${{ steps.build.outputs.EXTENSION }}
env:
GH_TOKEN: ${{ github.token }}
================================================
FILE: .github/workflows/ci.yml
================================================
# Based on https://github.com/actions-rs/meta/blob/master/recipes/quickstart.md
#
# While our "example" application has the platform-specific code,
# for simplicity we are compiling and testing everything on the Ubuntu environment only.
# For multi-OS testing see the `cross.yml` workflow.
on:
push:
pull_request:
branches: [master]
name: CI
jobs:
# check:
# name: Check
# runs-on: ubuntu-latest
# steps:
# - name: Checkout sources
# uses: actions/checkout@v2
# - name: Install stable toolchain
# uses: actions-rs/toolchain@v1
# with:
# profile: minimal
# toolchain: stable
# override: true
# - name: Run cargo check
# uses: actions-rs/cargo@v1
# continue-on-error: false
# with:
# command: check
test:
name: Tests
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- name: Prep environment to test compiled-in paths
run: |
mkdir /tmp/cheats-dir
touch /tmp/config-file
- name: Run cargo test
uses: actions-rs/cargo@v1
continue-on-error: false
env:
NAVI_PATH: /tmp/cheats-dir
NAVI_CONFIG: /tmp/config-file
with:
command: test
- name: Run cargo test
uses: actions-rs/cargo@v1
continue-on-error: false
with:
command: test
- name: Install deps
run: ./scripts/dot pkg add git bash npm tmux
- name: Install fzf
run: git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf; yes | ~/.fzf/install;
- name: Install tealdeer
run: sudo npm install -g tldr
- name: Run bash tests
run: ./tests/run
lints:
name: Lints
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v2
# - name: Install stable toolchain
# uses: actions-rs/toolchain@v1
# with:
# profile: minimal
# toolchain: stable
# override: true
# components: rustfmt, clippy
- name: Run cargo fmt
uses: actions-rs/cargo@v1
continue-on-error: false
with:
command: fmt
args: --all -- --check
- name: Run cargo clippy
uses: actions-rs/cargo@v1
continue-on-error: false
with:
command: clippy
args: -- -D warnings
================================================
FILE: .gitignore
================================================
/target
**/*.rs.bk
navi.log
================================================
FILE: Cargo.toml
================================================
[package]
name = "navi"
version = "2.25.0-beta1"
authors = ["Denis Isidoro <denis_isidoro@live.com>", "Alexis Opolka <alexis.opolka@protonmail.com>"]
edition = "2021"
description = "An interactive cheatsheet tool for the command-line"
homepage = "https://github.com/denisidoro/navi"
documentation = "https://github.com/denisidoro/navi"
repository = "https://github.com/denisidoro/navi"
keywords = ["cheatsheets", "terminal", "cli", "tui", "shell"]
categories = ["command-line-utilities"]
license = "Apache-2.0"
[features]
disable-command-execution = []
disable-repo-management = []
[badges]
travis-ci = { repository = "denisidoro/navi", branch = "master" }
[dependencies]
regex = { version = "1.7.3", default-features = false, features = [
"std",
"unicode-perl",
] }
clap = { version = "4.2.1", features = ["derive", "cargo"] }
crossterm = "0.28.0"
lazy_static = "1.4.0"
etcetera = "0.10.0"
walkdir = "2.3.3"
shellwords = "1.1.0"
anyhow = "1.0.70"
thiserror = "2.0.0"
strip-ansi-escapes = "0.2.0"
edit = "0.1.4"
remove_dir_all = "1.0.0"
serde = { version = "1.0.219", features = ["derive"] }
serde_yaml = "0.9.21"
unicode-width = "0.2.0"
tracing = "0.1.41"
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
[target.'cfg(windows)'.dependencies]
dunce = "1"
[lib]
name = "navi"
path = "src/lib.rs"
[[bin]]
name = "navi"
path = "src/bin/main.rs"
bench = false
================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: Makefile
================================================
export PATH := /usr/local/opt/bash/bin/:$(PATH)
install:
scripts/make install
uninstall:
scripts/make uninstall
fix:
scripts/make fix
test:
scripts/test
build:
cargo build
================================================
FILE: README.md
================================================
# navi <img src="https://raw.githubusercontent.com/denisidoro/navi/master/assets/icon.png" alt="icon" height="28px"/> [](https://github.com/denisidoro/navi/actions) 
An interactive cheatsheet tool for the command-line.
[](https://asciinema.org/a/406461)
**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.
## Pros
- it will spare you from knowing CLIs by heart
- it will spare you from copy-pasting output from intermediate commands
- it will make you type less
- it will teach you new one-liners
It 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).
## Table of contents
- [Installation](#installation)
- [Usage](#usage)
- [Cheatsheet repositories](#cheatsheet-repositories)
- [Cheatsheet syntax](#cheatsheet-syntax)
- [Customization](#customization)
- [More info](#more-info)
## Installation
The recommended way to install **navi** is by running:
```sh
brew install navi
```
> [!NOTE]
> For more details on how to install Navi, see [docs/installation](docs/installation/README.md)
**navi** can be installed with the following package managers:
[](https://repology.org/project/navi/versions)
## Usage
There are multiple ways to use **navi**:
- by typing `navi` in the terminal
- pros: you have access to all possible subcommands and flags
- as a [shell widget](docs/widgets/README.md#installing-the-shell-widget) for the terminal
- 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
- as a [Tmux widget](docs/widgets/howto/TMUX.md)
- pros: you can use your cheatsheets in any command-line app even in SSH sessions
- as [aliases](docs/cheatsheet/syntax/README.md#aliases)
- as a [shell scripting tool](docs/usage/shell-scripting/README.md)
In particular, check [these instructions](https://github.com/denisidoro/navi/issues/491) if you want to replicate what's shown in the demo above.
## Cheatsheet repositories
Running **navi** for the first time will help you download and manage cheatsheets. By default, they are stored at `~/.local/share/navi/cheats/`.
You can also:
- [browse through featured cheatsheets](docs/usage/commands/repo/README.md#browsing-through-cheatsheet-repositories)
- [import cheatsheets from git repositories](docs/cheatsheet/repositories/README.md#importing-cheatsheet-repositories)
- [write your own cheatsheets](#cheatsheet-syntax) (and [share them](docs/cheatsheet/repositories/README.md#submitting-cheatsheets), if you want)
- [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)
- [auto-update repositories](docs/cheatsheet/repositories/README.md#auto-updating-repositories)
- auto-export cheatsheets from your [TiddlyWiki](https://tiddlywiki.com/) notes using a [TiddlyWiki plugin](https://bimlas.github.io/tw5-navi-cheatsheet/)
## Cheatsheet syntax
Cheatsheets are described in `.cheat` files that look like this:
```sh
% git, code
# Change branch
git checkout <branch>
$ branch: git branch | awk '{print $NF}'
```
The full syntax and examples can be found [here](docs/cheatsheet/syntax/README.md).
## Customization
You can:
- [setup your own config file](docs/configuration/README.md)
- [set custom paths for your config file and cheat sheets](docs/configuration/README.md#paths-and-environment-variables)
- [change colors](docs/configuration/README.md#changing-colors)
- [resize columns](docs/configuration/README.md#resizing-columns)
- [change how search is performed](docs/configuration/README.md#overriding-fzf-options)
## More info
Please run the following command to read more about all possible options:
```sh
navi --help
```
In addition, please check the [/docs](docs) folder or the website.
================================================
FILE: docs/README.md
================================================
# Navi <img src="https://raw.githubusercontent.com/denisidoro/navi/master/assets/icon.png" alt="icon" height="28px"/> [](https://github.com/denisidoro/navi/actions) 
## Table of Contents
<!-- TOC -->
* [Navi <img src="https://raw.githubusercontent.com/denisidoro/navi/master/assets/icon.png" alt="icon" height="28px"/>  ](#navi-img-srchttpsrawgithubusercontentcomdenisidoronavimasterassetsiconpng-alticon-height28px--)
* [Table of Contents](#table-of-contents)
* [About](#about)
* [Navi Pros](#navi-pros)
* [Similar tools](#similar-tools)
* [Etymology](#etymology)
<!-- TOC -->
## About
Navi is an interactive cheatsheet tool for the command-line.\
It allows you to browse through cheatsheets (that you may write yourself or download from maintainers) and execute commands.
[](https://asciinema.org/a/406461)
It 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).
## Navi Pros
- it will spare you from knowing CLIs by heart
- it will spare you from copy-pasting output from intermediate commands
- it will make you type less
- it will teach you new one-liners
## Similar tools
There 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).
They are excellent projects, but **navi** remains unique in the following ways:
- it's natural to write cheatsheets tailored to your needs
- arguments are neither hardcoded nor a simple template
## Etymology
[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.
================================================
FILE: docs/cheatsheet/README.md
================================================
# Navi cheatsheets
<!-- TOC -->
* [Navi cheatsheets](#navi-cheatsheets)
* [Working with `cheatsheet repositories`](#working-with-cheatsheet-repositories)
* [Manually adding cheatsheets to navi](#manually-adding-cheatsheets-to-navi)
* [Choosing between queries and selection with variables](#choosing-between-queries-and-selection-with-variables)
* [Using cheatsheets from other tools](#using-cheatsheets-from-other-tools)
<!-- TOC -->
## Working with `cheatsheet repositories`
Navi works best with what we call `cheatsheet repositories`, for more details see [cheatsheet/repositories](repositories/README.md).
## Manually adding cheatsheets to navi
If you don't want to work with `cheatsheet repositories`, you can manually add your
cheatsheets to navi by putting them into the `cheats_path` of your platform.
You can find out your path using the [info](/docs/usage/commands/info/README.md) subcommands
but a quick working command to go there would be:
- Before 2.25.0
```bash
cd $(navi info cheats-path)
```
- After 2.25.0
```bash
cd $(navi info default-cheats-path)
```
## Choosing between queries and selection with variables
Navi lets you use different methods to fill a variable value, when prompted.
| Keyboard key | Preference |
|:------------------:|:--------------------------:|
| <kbd> tab </kbd> | The query is preferred |
| <kbd> enter </kbd> | The selection is preferred |
It means if you enter the <kbd> tab </kbd> key, navi will let you enter the value.
## Using cheatsheets from other tools
> [!WARNING]
> Navi **DOESN'T SUPPORT** as of now importing cheatsheets from other tools
> but is able to **work with** TLDR and Cheat.sh.

You can use cheatsheets from [tldr](https://github.com/tldr-pages/tldr) by running:
```sh
navi --tldr <query>
```
You can use cheatsheets from [cheat.sh](https://github.com/chubin/cheat.sh) by running:
```sh
navi --cheatsh <query>
```
================================================
FILE: docs/cheatsheet/getting-started/README.md
================================================
# Cheatsheets - Getting started
================================================
FILE: docs/cheatsheet/repositories/README.md
================================================
# Cheatsheet repositories
<!-- TOC -->
* [Cheatsheet repositories](#cheatsheet-repositories)
* [About](#about)
* [Importing cheatsheet repositories](#importing-cheatsheet-repositories)
* [Submitting cheatsheets](#submitting-cheatsheets)
* [Auto-updating repositories](#auto-updating-repositories)
<!-- TOC -->
## About
Navi lets you work with what we call `cheatsheet repositories`, they are git repositories
and mainly consists of `.cheat` files.
This page is dedicated to the information you might need to work with `cheatsheet repositories`.
## Importing cheatsheet repositories
You can import `cheatsheet repositories` with the `repo add` subcommand.\
See [/docs/usage/commands/repo](/docs/usage/commands/repo/README.md#importing-cheatsheet-repositories) for more details.
## Submitting cheatsheets
The featured repository for cheatsheets is [denisidoro/cheats](https://github.com/denisidoro/cheats),
feel free to open a PR[^1] there for me to include your contributions.
In 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].
## Auto-updating repositories
Right now, **navi** doesn't have support for auto-updating out of the box.
However, you can achieve this by using `git` and `crontab`.
- First make sure you cloned your repo using `git` to the correct folder:
```sh
user="<user>"
repo="<repo>"
git clone "https://github.com/${user}/${repo}" "$(navi info cheats-path)/${user}__${repo}"
```
- Then, add a cron job:
```sh
crontab -e
*/0 11 * * * bash -c 'cd "$(/usr/local/bin/navi info cheats-path)/<user>__<repo>" && /usr/local/bin/git pull -q origin master'
```
> [!NOTE]
> Please note the cron job above is just an example **AND** you should edit it accordingly:
>
>- In this example, the cron job is triggered every day at 11am.
>
> You might want to check out [crontab guru](https://crontab.guru/) regarding crontab.
>
>- The full paths to `navi` and `git` may differ in your setup.
>
> Check their actual values using `which` as `which <program>`.
>
>- Don't forget to replace `<user>__<repo>` with the actual folder name
[^1]: A *PR* is short for Pull Request
================================================
FILE: docs/cheatsheet/syntax/README.md
================================================
# The syntax of a Navi cheatsheet
<!-- TOC -->
* [The syntax of a Navi cheatsheet](#the-syntax-of-a-navi-cheatsheet)
* [Syntax overview](#syntax-overview)
* [Variables](#variables)
* [Advanced variable options](#advanced-variable-options)
* [Variable dependency](#variable-dependency)
* [Implicit dependencies](#implicit-dependencies)
* [Explicit dependencies](#explicit-dependencies)
* [Variable as multiple arguments](#variable-as-multiple-arguments)
* [Extending cheats](#extending-cheats)
* [Multiline commands/snippets](#multiline-commandssnippets)
* [Aliases](#aliases)
<!-- TOC -->
## Syntax overview
Cheats are described in cheatsheet files.\
A cheatsheet is a file that has a `.cheat` or `.cheat.md` extension and looks like this:
```sh
% git, code
# Change branch
git checkout <branch>
$ branch: git branch | awk '{print $NF}'
```
A cheatsheet can have the following elements:
| Element | Syntax | Description |
|:--------------------------------:|:------:|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|
| Tags as cheat titles | `%` | Lines starting with this character are considered the start of a new cheat command and should contain tags. |
| Cheat Description | `#` | Lines starting with this character should be the description of the cheat you're writing. |
| Cheat Comments (or Metacomments) | `;` | Lines starting with this character will be ignored by navi but they can be great as editor's comments. |
| 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. |
| 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. |
| Executable commands | N/A | All other non-empty lines are considered as executable commands. |
> [!TIP]
> If you are editing cheatsheets in Visual Studio Code, you could enable syntax highlighting
> by installing this extension: [@yanivmo/navi-cheatsheet-language](https://marketplace.visualstudio.com/items?itemName=yanivmo.navi-cheatsheet-language).
## Variables
Variables are defined with brackets inside executable commands (e.g. `<branch>`).\
Variable names should only include alphanumeric characters and `_`.
You can show suggestions by using the Pre-defined variable lines (i.e. lines starting with`$`).\
Otherwise, the user will be able to type any value for it.
### Advanced variable options
For Pre-Defined variable lines, you can use `---` to customize the behavior of `fzf`
or how the value is going to be used.
Below are examples of such customization:
- We define what column to use, the number of header lines and a delimiter between values.
```sh
# This will pick the 3rd column and use the first line as header
docker rmi <image_id>
$ image_id: docker images --- --column 3 --header-lines 1 --delimiter '\s\s+'
```
- We modify the output values of a command
```shell
# Even though "false/true" is displayed, this will print "0/1"
echo <mapped>
$ mapped: echo 'false true' | tr ' ' '\n' --- --map "grep -q t && echo 1 || echo 0"
```
The supported parameters are:
| Parameter | Description |
|:------------------------|:------------------------------------------------------------------------------------------|
| `--column <number>` | `<number>` is the column number to extract from the result. |
| `--map <bash_code>` | **_[EXPERIMENTAL]_** `<bash_code>` is a map function to apply to the variable value. |
| `--prevent-extra` | **_[EXPERIMENTAL]_** This parameter will limit the user to select one of the suggestions. |
| `--fzf-overrides <arg>` | **_[EXPERIMENTAL]_** `<arg>` is an arbitrary argument to override `fzf` behaviour. |
| `--expand` | **_[EXPERIMENTAL]_** This parameter will convert each line into a separate argument. |
In addition, it's possible to forward the following parameters to `fzf`:
| Parameter forwarded to `fzf` |
|:-----------------------------|
| `--multi` |
| `--header-lines <number>` |
| `--delimiter <regex>` |
| `--query <text>` |
| `--filter <text>` |
| `--header <text>` |
| `--preview <bash_code>` |
| `--preview-window <text>` |
### Variable dependency
Pre-Defined variables can refer other pre-defined variables in two different ways, an implicit and explicit way.
#### Implicit dependencies
An implicit dependency is when you refer another variable with the same syntax used in
executable commands (i.e. `<variable>`).
Below is an example of using implicit dependencies to construct a path:
```sh
# Should print /my/pictures/wallpapers
echo "<wallpaper_folder>"
$ pictures_folder: echo "/my/pictures"
$ wallpaper_folder: echo "<pictures_folder>/wallpapers"
```
#### Explicit dependencies
An explicit dependency is when you prepend a dollar sign (i.e. `$`) to the variable name.
Below is an example of using explicit dependencies to give multiple choices:
```sh
# If you select "hello" for <x>, the possible values of <y> will be "hello foo" and "hello bar"
echo <x> <y>
# If you want to ignore the contents of <x> and only print <y>
: <x>; echo <y>
$ x: echo "hello hi" | tr ' ' '\n'
$ y: echo "$x foo;$x bar" | tr ';' '\n'
```
### Variable as multiple arguments
Variables can have multiple arguments,
below is an example of using multiple arguments to cat multiple files at the same time.
```sh
# This will result into: cat "file1.json" "file2.json"
cat <jsons>
$ jsons: find . -iname '*.json' -type f -print --- --multi --expand
```
## Extending cheats
Navi allows you to extend a cheat context with `Extended cheats` lines (i.e. starting with `@`).\
If you put the same tags from another cheat, you will be able to share the same context and will
be able to use the same variables, for example.
```sh
% dirs, common
$ pictures_folder: echo "/my/pictures"
% wallpapers
@ dirs, common
# Should print /my/pictures/wallpapers
echo "<pictures_folder>/wallpapers"
% screenshots
@ dirs, common
# Should print /my/pictures/screenshots
echo "<pictures_folder>/screenshots"
```
## Multiline commands/snippets
Commands can be multiline, we call them snippets.
- You can write them as follows:
```sh
% bash, foo
# This will output "foo\nyes"
echo foo
true \
&& echo yes \
|| echo no
```
- Or, you can place them inside Markdown code blocks, delimited by triple backticks (```` ``` ````):
````sh
% git, code
# Change branch
```sh
git checkout <branch>
```
$ branch: git branch | awk '{print $NF}'
````
## Aliases
**navi** doesn't have support for aliases as first-class citizens at the moment.\
However, it is easy to create aliases using **navi** + a few conventions.
> [!CAUTION]
> The examples below will only work if you use **navi** as a shell scripting tool.
>
> See [/docs/usage/shell-scripting](/docs/usage/shell-scripting/README.md) for more details.
For example, suppose you decide to end some of your commands with `:: <some_alias>`:
```bash
% aliases
# This is one command :: el
echo lorem ipsum
# This is another command :: ef
echo foo bar
```
You could add something similar to this in your `.bashrc`-like file:
```bash
navialias() {
navi --query ":: $1" --best-match
}
alias el="navialias el"
alias ef="navialias ef"
```
If you don't want to use these conventions, you can even add full comments in your aliases:
```bash
navibestmatch() {
navi --query "$1" --best-match
}
alias el="navibestmatch 'This is one command'"
alias ef="navibestmatch 'This is another command'"
```
================================================
FILE: docs/configuration/README.md
================================================
# Configuring Navi
Navi allows you to configure it with a YAML configuration.
<!-- TOC -->
* [Configuring Navi](#configuring-navi)
* [Paths and Environment Variables](#paths-and-environment-variables)
* [The default configuration file path](#the-default-configuration-file-path)
* [Cheatsheets paths](#cheatsheets-paths)
* [The default cheatsheets path](#the-default-cheatsheets-path)
* [Defining the cheatsheets path with the environment variable](#defining-the-cheatsheets-path-with-the-environment-variable)
* [Defining the cheatsheets path in the configuration file](#defining-the-cheatsheets-path-in-the-configuration-file)
* [[DEPRECATED] - Using the `path` directive](#deprecated---using-the-path-directive)
* [Customization](#customization)
* [Changing colors](#changing-colors)
* [fzf color scheme](#fzf-color-scheme)
* [Navi colors](#navi-colors)
* [Resizing columns](#resizing-columns)
* [Overriding fzf options](#overriding-fzf-options)
* [Overriding during cheats selection](#overriding-during-cheats-selection)
* [Overriding during values selection](#overriding-during-values-selection)
* [Overriding for all cases](#overriding-for-all-cases)
* [Defining your own delimiter](#defining-your-own-delimiter)
<!-- TOC -->
## Paths and Environment Variables
On the technical side, navi uses the `directories-next` crate for rust,
which defines platform-specific locations to store the configuration files,
the cache and other types of files an application might need.
> [!TIP]
> For example, this is why cheatsheets are being stored in `~/Library/Application Support/navi` on macOS.
> [!NOTE]
> Interested on how `directories-next` works?\
> Go see their `crates.io` page: [crates.io/crates/directories-next](https://crates.io/crates/directories-next)
### The default configuration file path
During the compilation of navi, the default configuration file path is set by the `$NAVI_CONFIG` environment variable.\
If it is not set, it fallbacks to `~/.config/navi/config.yaml`.
You can check your default configuration file path with the info subcommand,
see [/docs/usage/commands/info/](/docs/usage/commands/info/README.md#default-configuration-path) for more details.
### Cheatsheets paths
Navi checks the paths in the following order until it finds a value:
1. the `$NAVI_PATH` environment variable
2. the configuration file
3. The default value of navi
#### The default cheatsheets path
By default, navi stores the cheatsheets in the `~/.local/share/navi/cheats/` directory.
You can check your default cheatsheets path with the info subcommand,
see [/docs/usage/commands/info/](/docs/usage/commands/info/README.md#default-cheatsheets-path) for more details.
#### Defining the cheatsheets path with the environment variable
The cheatsheets path can be defined using the `$NAVI_PATH` environment variable in a colon-separated list, for example:
```sh
export NAVI_PATH='/path/to/a/dir:/path/to/another/dir:/yet/another/dir'
```
#### Defining the cheatsheets path in the configuration file
You can define the cheatsheets path in the configuration file with the following syntax:
```yaml
cheats:
paths:
- /path/to/some/dir # on unix-like os
- F:\\path\\to\\dir # on Windows
```
##### [DEPRECATED] - Using the `path` directive
Until `2.17.0`, you could define your cheatsheets path with the `path` directive with the following syntax:
```yaml
cheats:
path: /path/to/some/dir
```
The directive is now deprecated and will be removed in `2.27.0`.
## Customization
### Changing colors
#### fzf color scheme
You can change the color scheme of `fzf` by overriding fzf options.
> [!NOTE]
> See [@junegunn/fzf/wiki/Color-schemes](https://github.com/junegunn/fzf/wiki/Color-schemes) and
> [#overriding-fzf-options](#overriding-fzf-options) for more details.
#### Navi colors
You can change the text color for each column of navi in the configuration file with the following syntax:
```yaml
style:
tag:
color: <your color for tags>
comment:
color: <your color for comments>
snippet:
color: <your color for snippets>
```
Below is an example of what to do if you'd like navi to look like the French flag:
- `config.yaml`:
```yaml
style:
tag:
color: blue
comment:
color: white
snippet:
color: red
```
- The result:

### Resizing columns
You can change the column width of each column of navi in the configuration file with the following syntax:
```yaml
style:
tag:
width_percentage: <width relative to the terminal window>
min_width: <width as number of characters>
comment:
width_percentage: <width relative to the terminal window>
min_width: <width as number of characters>
snippet:
width_percentage: <width relative to the terminal window>
min_width: <width as number of characters>
```
### Overriding fzf options
You can override fzf options for two different cases:
- During the cheats selection
Navi exposes the `overrides` directive in the configuration file
and the `NAVI_FZF_OVERRIDES` environment variable.
- During the pre-defined variable values selection
Navi exposes the `overrides_var` directive in the configuration file
and the `NAVI_FZF_OVERRIDES_VAR` environment variable.
For all cases, navi exposes the `FZF_DEFAULT_OPTS` environment variable.
#### Overriding during cheats selection
If you want to do the override with `--height 3`,
you can do it with the following syntax in the configuration file:
```yaml
finder:
command: fzf
overrides: --height 3
```
But you can also define the environment variable like this:
```bash
export NAVI_FZF_OVERRIDES='--height 3'
```
#### Overriding during values selection
If you want to do the override with `--height 3`,
you can do it with the following syntax in the configuration file:
```yaml
finder:
command: fzf
overrides_var: --height 3
```
But you can also define the environment variable like this:
```bash
export NAVI_FZF_OVERRIDES_VAR='--height 3'
```
#### Overriding for all cases
You can define the environment variable like this:
```bash
export FZF_DEFAULT_OPTS="--height 3"
```
> [!NOTE]
> See [@junegunn/fzf](https://github.com/junegunn/fzf#layout) for more details on `$FZF_DEFAULT_OPTS`.
## Defining your own delimiter
Navi allows you to define your own delimiter to parse the selected result for a variable in your cheats.\
It is equivalent to defining `--delimiter` used with `--column`.
You can define it as such:
```yaml
finder:
delimiter_var: <your-regex-delimiter> ### By default the expression is \s\s+
```
> [!CAUTION]
> Defining the delimiter via the configuration file means that Navi will use this delimiter by default for
> every variable using the `--column` instruction.
You can override this configuration with the `--delimiter` instruction in the variable definition of your cheat.\
See [/docs/cheatsheet/syntax/](/docs/cheatsheet/syntax/README.md#advanced-variable-options) for more details.
================================================
FILE: docs/contributions/README.md
================================================
# Navi contributors
This section is about the ways you can contribute to Navi and its ecosystem.
<!-- TOC -->
* [Navi contributors](#navi-contributors)
* [How to contribute to Navi](#how-to-contribute-to-navi)
* [Versioning Scheme](#versioning-scheme)
* [Deprecation of features](#deprecation-of-features)
<!-- TOC -->
## How to contribute to Navi
You have multiple ways to contribute to navi, here are the documented ones:
- [Write code for Navi](code/README.md)
- [Write documentation for Navi](documentation/README.md)
- [Open Bug tickets](bugs/README.md)
Please see each section for more details.
## Versioning Scheme
| Type | Description |
|-------|--------------------------------------------------------------------------------------------------|
| Major | Anything which introduces a major breaking change. The bash to rust rewrite was such an example. |
| Minor | Almost everything. |
| Fix | A fix, just like its name. It should be micro releases with minimal changes. |
## Deprecation of features
Once you introduce a feature, you need to have a clear view of when we're
going to remove its support within navi.
In order to offer stability to the users, we prefer having 10 minor versions
between the deprecation notice and the removal of its support.
````txt
Version where the feature is being deprecated: 0.10.0
Version where the support is dropped: 0.20.0
````
> [!NOTE]
> This rule is not absolute and each feature deprecation needs to be handled
> carefully given its own circumstances, but try to stick as close as possible
> to this rule.
================================================
FILE: docs/contributions/bugs/README.md
================================================
# Contribute in opening bug tickets
Like any other software, navi has bugs.
If you encounter an issue with Navi, we encourage you to open a bug ticket.\
Please see [https://github.com/denisidoro/navi/issues/](https://github.com/denisidoro/navi/issues/) to open an issue.
================================================
FILE: docs/contributions/code/README.md
================================================
# Contribute code to Navi
Navi is written in Rust, the widgets may be written in any language given it can be integrated with Navi.
If you take the example of the most common widgets for Navi they are written in their shell scripting language
because they intend to integrate Navi with the shell in question (Fish, Zsh, NuShell, PowerShell, etc.).
We separate Navi into two categories:
- `Navi Core` which refers to Navi's code in Rust
- `Navi Widgets` which refers to code that intends to integrate Navi with a 3rd-party software
## Contribute to Navi Core
If you want to contribute to Navi Core there are certain steps you need to follow for
your changes to be accepted.
1. First, open an issue if no opened issues are related to the change you want to contribute.
2. [Optional] Wait to have an opinion from the maintainers, developers or contributors from Navi.
> This step is marked as *Optional* as you can open a Merge Request (MR)/Pull Request (PR)
> without having to open an issue beforehand, although it is recommended to not do so.
We ask you to wait before working on a PR as the way you see a feature and its implementation
might not be similar on how a maintainer of Navi sees it.
This will save you and the maintainers time.
3. Fork the repository and iterate over your changes.
4. Update Navi documentation
If you implement a new feature, you will need to create a new entry in the project's
documentation for users to know what has changed.
No significant modification in Navi's behaviour should be merged without being documented.\
For more details I recommend you to see [contributions/documentation/](../documentation/README.md).
5. Open a PR on [denisidoro/navi](https://github.com/denisidoro/navi/pulls) and request a review
6. [Optional] Your PR needs revisions and changes before it can be merged
It's not rare that your PR will need changes before it can be accepted by the maintainers
and then merged into the main branch.
7. Your PR has been merged
Congratulations! Your PR has been reviewed and merged, you should be proud of it,
and we thank you for your contribution.
The next release cycle will package all contributions into a new release and users
throughout the world will be able to use your new feature(s).
================================================
FILE: docs/contributions/documentation/README.md
================================================
# Contribute documentation to Navi
If you don't want or can't code in Rust, we welcome all contributions,
even more so if it's related to documentation.
The documentation of Navi is currently made in Markdown.
## Markdown documentation
The documentation source files are located in the `docs/` folder and are mainly grouped by features.
The current documentation follows a structure where one folder equals one topic.
Here is a quick representation of the folder structure this documentation currently follows:
```txt
.
+-- docs
| +-- examples
| | +-- <topic-examples>
| +-- src
| | +-- <topic-source-files>
| | | +-- <sorted-by-type>
| +-- <topic>
| | +-- README.md
```
You can see that we have separated the `src` and `examples` folder from the topic with the intent to make it
easier to find each type of documentation.
> [!NOTE]
> It is recommended to not go deeper than 3 levels in the documentation.
================================================
FILE: docs/deprecated/Alfred/README.md
================================================
# Alfred
> [!CAUTION]
> This feature has been deprecated and support has been dropped since 2.16.0.
>
> The latest version with support is [2.15.1](https://github.com/denisidoro/navi/releases/tag/v2.15.1).
This is _experimental_. If you face any issues, please report [here](https://github.com/denisidoro/navi/issues/348).

### Instructions
- make sure you have [Alfred Powerpack](https://www.alfredapp.com/powerpack/)
- make sure **navi** [2.15.1](https://github.com/denisidoro/navi/releases/tag/v2.15.1) is installed
- make sure that the `navi` binary is in the `$PATH` determined by `~/.bashrc`
- download and install the [latest .alfredworkflow available](https://github.com/denisidoro/navi/releases/tag/v2.15.1)
================================================
FILE: docs/examples/cheatsheet/example.cheat
================================================
% first cheat
# print something
echo "My name is <name>!"
$ name: whoami
================================================
FILE: docs/examples/cheatsheet/navi.cheat
================================================
% cheatsheets
# Download default cheatsheets
navi repo add denisidoro/cheats
# Browse for cheatsheet repos
navi repo browse
# Edit main local cheatsheets
f="$(navi info cheats-path)/main.cheat"
[ -f "$f" ] || navi info cheats-example > "$f"
${EDITOR:-nano} "$f"
% config
# Edit config file
f="$(navi info config-path)"
[ -f "$f" ] || navi info config-example > "$f"
${EDITOR:-nano} "$f"
% 3rd-party
# Search using tldr
navi --tldr "<query>"
# Search using cheatsh
navi --cheatsh "<query>"
% widget
# Load shell widget
shell="$(basename $SHELL)"; eval "$(navi widget $shell)"
% help
# Read command-line help text
navi --help
# Read project README.md
navi fn url::open "https://github.com/denisidoro/navi"
================================================
FILE: docs/examples/configuration/config-example.yaml
================================================
# THIS IS EXPERIMENTAL
# the config file schema may change at any time
style:
tag:
color: cyan # text color. possible values: https://bit.ly/3gloNNI
width_percentage: 26 # column width relative to the terminal window
min_width: 20 # minimum column width as number of characters
comment:
color: blue
width_percentage: 42
min_width: 45
snippet:
color: white
finder:
command: fzf # equivalent to the --finder option
# overrides: --tac # equivalent to the --fzf-overrides option
# overrides_var: --tac # equivalent to the --fzf-overrides-var option
# 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
# cheats:
# paths:
# - /path/to/some/dir # on unix-like os
# - F:\\path\\to\\dir # on Windows
# search:
# tags: git,!checkout # equivalent to the --tag-rules option
# client:
# tealdeer: true # enables tealdeer support for navi --tldr
shell:
# Shell used for shell out. Possible values: bash, zsh, dash, ...
# For Windows, use `cmd.exe` instead.
command: bash
# finder_command: bash # similar, but for fzf's internals
================================================
FILE: docs/installation/README.md
================================================
# Installation of navi
This is a reference of all known methods to install navi.
> [!CAUTION]
> Navi, as of now, has only two official builds, the released binaries on GitHub
> and the published package on brew.
>
> All the other packages are community-maintained.
## Using package managers
### Homebrew
```sh
brew install navi
```
> [!NOTE]
> See [brew.sh](https://brew.sh/) for more details.
### Using Gentoo
> [!WARNING]
> You need to enable the GURU overlay for the instructions below to work correctly.
>
> For more details see:
>
> - [wiki.gentoo.org/wiki/Ebuild_repository](https://wiki.gentoo.org/wiki/Ebuild_repository)
> - [gpo.zugaina.org/Overlays/guru/app-misc/navi](https://gpo.zugaina.org/Overlays/guru/app-misc/navi).
```sh
emerge -a app-misc/navi
```
> [!NOTE]
> See [Gentoo.org](https://gentoo.org/) for more details.
### Using Pacman
```sh
pacman -S navi
```
> [!NOTE]
> See [wiki.archlinux.org/title/Pacman](https://wiki.archlinux.org/title/Pacman) for more details.
### Using nix
```sh
nix-env -iA nixpkgs.navi
```
> [!NOTE]
> See [nixos.org](https://nixos.org/) for more details
### Using Cargo
```bash
cargo install --locked navi
```
> [!NOTE]
> See [@rust-lang/cargo](https://github.com/rust-lang/cargo) for more details.
### Using Chocolatey
```bash
choco install navi
```
> [!CAUTION]
> You currently need to create the config file `$env:USERPROFILE\AppData\Roaming\navi\config.yaml`
> and define the `shell.command` directive as `powershell` for navi to work correctly.
>
> ```yaml
> shell:
> command: powershell
> ```
> [!NOTE]
> See [community.chocolatey.org](https://community.chocolatey.org) for more details.
## Using the installation script
Navi has an installation script ready for you to use, you can call it like this:
```bash
bash <(curl -sL https://raw.githubusercontent.com/denisidoro/navi/master/scripts/install)
```
If you need to define the directory for the binary, you can call it like this:
```bash
BIN_DIR=/usr/local/bin bash <(curl -sL https://raw.githubusercontent.com/denisidoro/navi/master/scripts/install)
```
## Downloading pre-compiled binaries
With each release, we try our best to build and publish a binary for each
supported platform, you can find them here:
[@denisidoro/navi/releases/latest](https://github.com/denisidoro/navi/releases/latest)
What you need to do is:
- to download the binary corresponding to the version you want to install
- to extract the content of the archive to your `$PATH`
## Building from source
You can also build navi from source, it's mainly used by contributors to
test their modifications but can be used by end users who want to build their own version.
- You need to clone the repository:
```bash
git clone https://github.com/denisidoro/navi && cd navi
```
- Call `make`
```bash
make install
```
You can specify the binary directory with:
```bash
make BIN_DIR=/usr/local/bin install
```
## Compile time environment variables
**navi** supports environment variables at compile time that will modify the behavior of navi at runtime, they are:
| Environment variable | Description |
|----------------------|-------------------------------------------------------------|
| `NAVI_PATH` | This defines the default path used by navi for cheatsheets. |
| `NAVI_CONFIG` | This defines the default configuration file used by navi. |
## Other package managers
You can find **navi** for more package managers by clicking on the image below:
[](https://repology.org/project/navi/versions)
Feel free to be the maintainer of **navi** for any package manager you'd like!
================================================
FILE: docs/usage/README.md
================================================
# The usage of Navi
Navi can be used in multiple ways
#### Defining the cheatsheets path at runtime
You can define the paths to use for cheatsheets at runtime using the `--path` parameter and a colon-separated paths list
For example, if we want to search for cheatsheets in `/some/dir` and in `/other/dir`:
```sh
navi --path '/some/dir:/other/dir'
```
## Logging
The log file will be created under the same directory where the configuration file is located.\
You can use the `RUST_LOG` environment variable to set the log level.
For example, to have the logging in debug mode when running navi:
```bash
RUST_LOG=debug navi
```
> [!NOTE]
> If the directory of the configuration file doesn't exist, no log file
> is going to be created.
================================================
FILE: docs/usage/commands/info/README.md
================================================
# The info subcommands of navi
Navi exposes information about its default values or examples for you to use.
<!-- TOC -->
* [The info subcommands of navi](#the-info-subcommands-of-navi)
* [Commands Reference](#commands-reference)
* [Default configuration information](#default-configuration-information)
* [Default configuration path](#default-configuration-path)
* [Example configuration file](#example-configuration-file)
* [Default cheatsheets path](#default-cheatsheets-path)
<!-- TOC -->
## Commands Reference
| Command | Description |
|---------------------|----------------------------------------------------|
| config-path | [DEPRECATED] Lets you see the default config path |
| cheats-path | [DEPRECATED] Lets you see the default cheats path |
| default-config-path | Lets you see the default config path |
| default-cheats-path | Lets you see the default cheats path |
| config-example | Lets you see an example for the configuration file |
| cheats-example | Lets you see an example for a cheat file |
## Default configuration information
### Default configuration path
Navi exposes its default configuration path with:
```sh
navi info config-path
```
> [!NOTE]
> See [/docs/configuration/](/docs/configuration/README.md#the-default-configuration-file-path) for more details on how the default configuration path is defined.
### Example configuration file
Navi lets you get an example configuration file with:
```sh
navi info config-example
```
> [!NOTE]
> You can retrieve this file at the following address: [/docs/examples/configuration/config-example.yaml](/docs/examples/configuration/config-example.yaml)
For example, you can use this command to create the default configuration file,
if not already present:
```sh
navi info config-example > "$(navi info config-path)"
```
## Default cheatsheets path
Navi exposes its default cheatsheets path with:
```sh
navi info cheats-path
```
> [!NOTE]
> See [/docs/configuration/](/docs/configuration/README.md#the-default-cheatsheets-path) for more details on how the cheatsheets path is defined.
================================================
FILE: docs/usage/commands/repo/README.md
================================================
# The repo subcommands of navi
<!-- TOC -->
* [The repo subcommands of navi](#the-repo-subcommands-of-navi)
* [Commands Reference](#commands-reference)
* [Browsing through cheatsheet repositories](#browsing-through-cheatsheet-repositories)
* [Importing cheatsheet repositories](#importing-cheatsheet-repositories)
<!-- TOC -->
## Commands Reference
| Command | Description |
|---------|-------------------------------------------------------------------|
| add | Lets you import a cheatsheet repository |
| browser | Lets you browse through a curated list of cheatsheet repositories |
## Browsing through cheatsheet repositories
Navi 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).
You can find them within navi with the following command:
```sh
navi repo browse
```
## Importing cheatsheet repositories
You can import `cheatsheet repositories` using a working git-clone format.\
This includes using an HTTPS URL or an SSH URI.
- Import using HTTPS
```sh
navi repo add https://github.com/denisidoro/cheats
```
- Import using SSH
```shell
navi repo add git@github.com:denisidoro/cheats
```
> [!CAUTION]
> Despite `$NAVI_PATH` being set, it will not be used when installing cheat sheets directly via navi's own commands.\
> For example when running `navi add repo <repo>`, the default paths will still be used.
>
> To avoid this, you may simply clone repos via a regular `git clone` command, directly into `$NAVI_PATH`.
================================================
FILE: docs/usage/fzf-overrides/README.md
================================================
# The FZF Overrides of Navi
Navi allows you to override certain parts of FZF in multiple ways.
<!-- TOC -->
* [The FZF Overrides of Navi](#the-fzf-overrides-of-navi)
* [Command line arguments](#command-line-arguments)
* [Environment variables](#environment-variables)
<!-- TOC -->
## Command line arguments
Navi allows you to use command line arguments in order to override fzf values:
```sh
# if you want to override only when selecting snippets
navi --fzf-overrides '--height 3'
# if you want to override only when selecting argument values
navi --fzf-overrides-var '--height 3'
```
## Environment variables
Navi allows you to use environment variables in order to override fzf values.
```bash
# if you want to override for all cases
FZF_DEFAULT_OPTS="--height 3" navi
```
================================================
FILE: docs/usage/shell-scripting/README.md
================================================
# Navi and shell scripting
You can use Navi with shell scripting.
<!-- TOC -->
* [Navi and shell scripting](#navi-and-shell-scripting)
* [Simply calling a cheat](#simply-calling-a-cheat)
* [Defining variables while calling](#defining-variables-while-calling)
* [Filtering results for a variable](#filtering-results-for-a-variable)
* [Selecting the best match for a variable](#selecting-the-best-match-for-a-variable)
<!-- TOC -->
> [NOTE!]
> 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/)
## Simply calling a cheat
Below is an example on how to call a cheat from within navi:
```sh
navi --query "change branch" --best-match
```
> [!NOTE]
> Navi will ask the user to fill all arguments/variables needed.
## Defining variables while calling
If you want to set the `<branch>` beforehand in your script, you can do as follows:
```sh
branch="master" navi --query "change branch" --best-match
```
Navi will not show any interactive input and `<branch>` will be exactly the one defined while calling.
## Filtering results for a variable
If you want to filter some results for `<branch>`, you can do as follows:
```sh
branch__query="master" navi --query "change branch" --best-match
```
Navi will show any interactive input, unless a single entry is automatically selected and
the value for `<branch>` will be the one selected by the user.
## Selecting the best match for a variable
If you want to select the best match for `<branch>`, you can do as follows:
```sh
branch__best="master" navi --query "change branch" --best-match
```
Navi will not show any interactive input, and the value for `<branch>` will be the one that
best matches the value passed as argument.
================================================
FILE: docs/widgets/README.md
================================================
# Navi widgets
You want to launch Navi with a shortcut?\
Widgets are here for you!
Widgets are 3rd-party contributions and integrates Navi with 3rd-party software such as shells.
## List of shell widgets
| Shell | Navi support |
|------------|--------------------|
| Bash | :white_check_mark: |
| Fish | |
| Zsh | |
| NuShell | :white_check_mark: |
| PowerShell | :white_check_mark: |
## PowerShell Widget
- Removal
```powershell
Remove-Module navi.plugin
```
## Other widgets
- Tmux
- Vim
### Installing the shell widget
If you want to install it, add this line to your `.bashrc`-like file:
```sh
# bash
eval "$(navi widget bash)"
# zsh
eval "$(navi widget zsh)"
# fish
navi widget fish | source
# elvish
eval (navi widget elvish | slurp)
# xonsh
# xpip install xontrib-navi # ← run in your xonsh session to install xontrib
xontrib load navi # ← add to your xonsh run control file
```
#### Nushell
Due 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:
1. run `^navi widget nushell | save ($nu.default-config-dir | path join "navi-integration.nu")`
2. add the following lines to `config.nu`:
```nushell
source ($nu.default-config-dir | path join "navi-integration.nu")
```
By 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).
There'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:
1. run, e.g., `navi widget bash` in your terminal
2. copy the output
3. paste the output in your `.bashrc`-like file
4. edit the contents accordingly
================================================
FILE: docs/widgets/howto/TMUX.md
================================================
# Tmux widget
You can use **navi** as a [Tmux](https://github.com/tmux/tmux/wiki) widget to reach your Vim commands,
often used SQL queries, etc. in any command-line app even in SSH sessions.
<!-- TOC -->
* [Tmux widget](#tmux-widget)
* [Keybinding navi](#keybinding-navi)
* [Example cheatsheet](#example-cheatsheet)
<!-- TOC -->
## Keybinding navi
To be able to open navi via <kbd> prefix + C-g </kbd>, you need to add the following lines
to your Tmux configuration file.
```sh
bind-key -N "Open Navi (cheat sheets)" -T prefix C-g split-window \
"$SHELL --login -i -c 'navi --print | tmux load-buffer -b tmp - ; tmux paste-buffer -p -t {last} -b tmp -d'"
```
## Example cheatsheet
Here is an example cheatsheet to use inside Tmux:
```sh
% vim
# Quit without save
qa!
# Delete a paragraph
normal dap
# Generate sequence of numbers
put =range(<start>, <stop>)
% postgresql
# Describe table columns in `psql` or `pgcli`
select
table_name,
column_name,
data_type
from
information_schema.columns
where
table_name = '<table>';
```
================================================
FILE: docs/widgets/howto/VIM.md
================================================
# Vim widget
<!-- TOC -->
* [Vim widget](#vim-widget)
* [Syntax Highlighting](#syntax-highlighting)
<!-- TOC -->
## Syntax Highlighting
If you want syntax highlighting support for Navi in Vim, you need to
add those syntax rules to your syntax files such as at `$VIMRUNTIME/syntax/navi.vim`.
The rules are defined based on the [Cheatsheet syntax](/docs/cheatsheet/syntax/README.md).
Here is an example:
```vim
syntax match Comment "\v^;.*$"
syntax match Statement "\v^\%.*$"
syntax match Operator "\v^\#.*$"
syntax match String "\v\<.{-}\>"
syntax match String "\v^\$.*$"
```
================================================
FILE: rust-toolchain.toml
================================================
[toolchain]
channel = "1.81.0"
components = [ "rustfmt", "clippy" ]
================================================
FILE: rustfmt.toml
================================================
max_width = 110
================================================
FILE: scripts/docker
================================================
#!/usr/bin/env bash
set -euo pipefail
export NAVI_HOME="$(cd "$(dirname "$0")/.." && pwd)"
_start() {
cd "$NAVI_HOME"
./scripts/release x86_64-unknown-linux-musl
docker run \
-e HOMEBREW_NO_AUTO_UPDATE=1 \
-e HOMEBREW_NO_INSTALL_CLEANUP=1 \
-v "$(pwd):/navi" \
-it 'bashell/alpine-bash' \
bash -c '/navi/scripts docker setup; exec bash'
}
_setup() {
apk add git
apk add curl
git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf
ln -s /navi/target/debug/navi /usr/local/bin/navi
}
main() {
local -r fn="$1"
shift || true
"_${fn}" "$@"
}
main "$@"
================================================
FILE: scripts/dot
================================================
#!/usr/bin/env bash
set -euo pipefail
export NAVI_HOME="$(cd "$(dirname "$0")/.." && pwd)"
export PROJ_HOME="$NAVI_HOME"
export PROJ_NAME="navi"
export CARGO_PATH="${NAVI_HOME}/core/Cargo.toml"
# TODO: bump dotfiles + remove this fn
log::note() { log::info "$@"; }
cargo() {
if [ "${1:-}" = "install" ] && [ "${2:-}" = "cross" ]; then
shift 2 || true
command cargo install cross --git https://github.com/cross-rs/cross "$@"
else
command cargo "$@"
fi
}
export -f log::note cargo
dot::clone() {
git clone 'https://github.com/denisidoro/dotfiles' "$DOTFILES"
cd "$DOTFILES"
git checkout 'v2022.07.16'
}
dot::clone_if_necessary() {
[ -n "${DOTFILES:-}" ] && [ -x "${DOTFILES}/bin/dot" ] && return
export DOTFILES="${NAVI_HOME}/target/dotfiles"
dot::clone
}
dot::clone_if_necessary
"${DOTFILES}/bin/dot" "$@"
================================================
FILE: scripts/install
================================================
#!/usr/bin/env bash
set -euo pipefail
if ${X_MODE:-false}; then
set -x
fi
# =====================
# paths
# =====================
export CARGO_DEFAULT_BIN="${HOME}/.cargo/bin"
export BIN_DIR="${BIN_DIR:-"$CARGO_DEFAULT_BIN"}"
# =====================
# logging
# =====================
echoerr() {
echo "$@" 1>&2
}
tap() {
local -r x="$(cat)"
echoerr "$x"
echo "$x"
}
log::ansi() {
local bg=false
case "$@" in
*reset*) echo "\e[0m"; return 0 ;;
*black*) color=30 ;;
*red*) color=31 ;;
*green*) color=32 ;;
*yellow*) color=33 ;;
*blue*) color=34 ;;
*purple*) color=35 ;;
*cyan*) color=36 ;;
*white*) color=37 ;;
esac
case "$@" in
*regular*) mod=0 ;;
*bold*) mod=1 ;;
*underline*) mod=4 ;;
esac
case "$@" in
*background*) bg=true ;;
*bg*) bg=true ;;
esac
if $bg; then
echo "\e[${color}m"
else
echo "\e[${mod:-0};${color}m"
fi
}
_log() {
local template="$1"
shift
echoerr "$(printf "$template" "$@")"
}
_header() {
local TOTAL_CHARS=60
local total=$TOTAL_CHARS-2
local size=${#1}
local left=$((($total - $size) / 2))
local right=$(($total - $size - $left))
printf "%${left}s" '' | tr ' ' =
printf " $1 "
printf "%${right}s" '' | tr ' ' =
}
log::header() { _log "\n$(log::ansi bold)$(log::ansi purple)$(_header "$1")$(log::ansi reset)\n"; }
log::success() { _log "$(log::ansi green)✔ %s$(log::ansi reset)\n" "$@"; }
log::error() { _log "$(log::ansi red)✖ %s$(log::ansi reset)\n" "$@"; }
log::warning() { _log "$(log::ansi yellow)➜ %s$(log::ansi reset)\n" "$@"; }
log::note() { _log "$(log::ansi blue)%s$(log::ansi reset)\n" "$@"; }
# TODO: remove
header() {
echoerr "$*"
echoerr
}
die() {
log::error "$@"
exit 42
}
no_binary_warning() {
log::note "There's no precompiled binary for your platform: $(uname -a)"
}
installation_finish_instructions() {
local -r shell="$(get_shell)"
log::note -e "Finished. To call navi, restart your shell or reload the config file:\n source ~/.${shell}rc"
local code
if [[ "$shell" == "zsh" ]]; then
code="navi widget ${shell} | source"
else
code='source <(navi widget '"$shell"')'
fi
log::note -e "\nTo add the Ctrl-G keybinding, add the following to ~/.${shell}rc:\n ${code}"
}
# =====================
# security
# =====================
sha256() {
if command_exists sha256sum; then
sha256sum
elif command_exists shasum; then
shasum -a 256
elif command_exists openssl; then
openssl dgst -sha256
else
log::note "Unable to calculate sha256!"
exit 43
fi
}
# =====================
# github
# =====================
latest_version_released() {
curl -s 'https://api.github.com/repos/denisidoro/navi/releases/latest' \
| grep -Eo '"html_url": "https://github.com/denisidoro/navi/releases/tag/v([0-9\.]+)' \
| sed 's|"html_url": "https://github.com/denisidoro/navi/releases/tag/v||'
}
asset_url() {
local -r version="$1"
local -r variant="${2:-}"
if [[ -n "$variant" ]]; then
echo "https://github.com/denisidoro/navi/releases/download/v${version}/navi-v${version}-${variant}.tar.gz"
else
echo "https://github.com/denisidoro/navi/archive/v${version}.tar.gz"
fi
}
download_asset() {
local -r tmp_dir="$(mktemp -d -t navi-install-XXXX)"
local -r url="$(asset_url "$@")"
log::note "Downloading ${url}..."
cd "$tmp_dir"
curl -L "$url" -o navi.tar.gz
tar xvzf navi.tar.gz
mkdir -p "${BIN_DIR}" &>/dev/null || true
mv "./navi" "${BIN_DIR}/navi"
}
sha_for_asset_on_github() {
local -r url="$(asset_url "$@")"
curl -sL "$url" | sha256 | awk '{print $1}'
}
error_installing() {
log::error "Unable to install navi. Please check https://github.com/denisidoro/navi for alternative installation instructions"
exit 33
}
# =====================
# code
# =====================
version_from_toml() {
cat "${NAVI_HOME}/Cargo.toml" \
| grep version \
| head -n1 \
| awk '{print $NF}' \
| tr -d '"' \
| tr -d "'"
}
# =====================
# platform
# =====================
command_exists() {
type "$1" &>/dev/null
}
get_target() {
local -r unamea="$(uname -a)"
local -r archi="$(uname -sm)"
local target
case "$unamea $archi" in
*arwin*) target="x86_64-apple-darwin" ;;
*inux*x86*) target="x86_64-unknown-linux-musl" ;;
*ndroid*aarch*|*ndroid*arm*) target="aarch64-linux-android" ;;
*inux*aarch*|*inux*arm*) target="armv7-unknown-linux-musleabihf" ;;
*) target="" ;;
esac
echo "$target"
}
get_shell() {
echo $SHELL | xargs basename
}
# =====================
# main
# =====================
export_path_cmd() {
echo
echo ' export PATH="${PATH}:'"$1"'"'
}
append_to_file() {
local -r path="$1"
local -r text="$2"
if [ -f "$path" ]; then
echo "$text" >> "$path"
fi
}
get_navi_bin_path() {
local file="${BIN_DIR}/navi"
if [ -f "$file" ]; then
echo "$file"
return 0
fi
file="${CARGO_DEFAULT_BIN}/navi"
if [ -f "$file" ]; then
echo "$file"
return 0
fi
}
install_navi() {
local -r target="$(get_target)"
if command_exists navi; then
log::success "navi is already installed"
exit 0
elif command_exists brew; then
brew install navi
elif [[ -n "$target" ]]; then
local -r version="$(latest_version_released)"
download_asset "$version" "$target" || error_installing
elif command_exists cargo; then
cargo install navi
else
error_installing
fi
hash -r 2>/dev/null || true
local navi_bin_path="$(which navi || get_navi_bin_path)"
ln -s "$navi_bin_path" "${BIN_DIR}/navi" &>/dev/null || true
if [ -f "${BIN_DIR}/navi" ]; then
navi_bin_path="${BIN_DIR}/navi"
fi
local -r navi_bin_dir="$(dirname "$navi_bin_path")"
echoerr
log::success "Finished"
log::success "navi is now available at ${navi_bin_path}"
echoerr
if echo "$PATH" | grep -q "$navi_bin_dir"; then
:
else
local -r cmd="$(export_path_cmd "$navi_bin_dir")"
append_to_file "${HOME}/.bashrc" "$cmd"
append_to_file "${ZDOTDIR:-"$HOME"}/.zshrc" "$cmd"
append_to_file "${HOME}/.fishrc" "$cmd"
fi
log::note "To call navi, restart your shell or reload your .bashrc-like config file"
echo
log::note "Check https://github.com/denisidoro/navi for more info"
export PATH="${PATH}:${navi_bin_dir}"
return 0
}
(return 0 2>/dev/null) || install_navi "$@"
================================================
FILE: scripts/make
================================================
#!/usr/bin/env bash
set -euo pipefail
##? make install
##? make uninstall
export NAVI_HOME="$(cd "$(dirname "$0")/.." && pwd)"
source "${NAVI_HOME}/scripts/install"
install() {
cargo install --path .
}
uninstall() {
cargo uninstall
}
fix() {
"${NAVI_HOME}/scripts/fix"
}
cmd="$1"
shift
export X_MODE=true
set -x
case "$cmd" in
"install") install "$@" ;;
"uninstall") uninstall "$@" ;;
"fix") fix "$@" ;;
esac
================================================
FILE: scripts/release
================================================
#!/usr/bin/env bash
set -euo pipefail
### --------------------------------------------------------------------------------------------------------------------
### Logging functions
### --------------------------------------------------------------------------------------------------------------------
log::info() {
### Will print `[INFO]` in black foreground colour and magenta background colour
### then will print the given text in a magenta foreground colour and default background colour.
printf "\033[35m\033[7m[INFO]\033[27;39m \033[35m$*\033[39m\n"
}
log::error() {
### Will print `[ERROR]` in black foreground colour and red background colour
### then will print the given text in a red foreground colour and default background colour.
printf "\033[31m\033[7m[ERROR]\033[27;39m \033[31m$*\033[39m\n"
}
log::warn() {
### Will print `[WARNING]` in black foreground colour and yellow background colour
### then will print the given text in a yellow foreground colour and default background colour.
printf "\033[33m\033[7m[WARNING]\033[27;39m \033[33m$*\033[39m\n"
}
### --------------------------------------------------------------------------------------------------------------------
### Utils functions
### --------------------------------------------------------------------------------------------------------------------
### Permits us to know if the current target environment
### is a windows platform or not.
is_windows() {
local -r target="$1"
echo "$target" | grep -q "windows"
}
### NOTE: This function is currently not in use but kept as
### a backup function in case something breaks
###
### Returns the target environment, with a fix for the x86_64 target.
get_env_target() {
eval "$(rustc --print cfg | grep target)"
local -rr raw="${target_arch:-}-${target_vendor:-}-${target_os:-}-${target_env:-}"
if echo "$raw" | grep -q "x86_64-apple-macos"; then
echo "x86_64-apple-darwin"
else
echo "$raw"
fi
}
### NOTE: This function is currently not in use but kept as
### a backup function in case something breaks
###
### Logs the given arguments then execute it
_tap() {
log::info "$@"
"$@"
}
### NOTE: This function is currently not in use but kept as
### a backup function in case something breaks
###
### Lists the content of a path, given as parameter.
_ls() {
log::info "contents from $*:"
ls -la "$@" || true
}
### --------------------------------------------------------------------------------------------------------------------
### Release-Related functions
### --------------------------------------------------------------------------------------------------------------------
release() {
local -r env_target="$1"
log::info "env target: $env_target"
local -r cross_target="${1:-"$env_target"}"
log::info "desired target: $cross_target"
TAR_DIR="$(pwd)/target/tar"
### We clean up the target folder, just in case
rm -rf "$(pwd)/target" 2> /dev/null || true
### We add the target for rustup in case cross doesn't find it.
### Since the default behaviour of cross is to compile from
### a rustup target if it doesn't find one for itself.
rustup target add $env_target
cargo install cross --locked
### We're building the release via cross for the target environment
cross build --release --target "$env_target"
cd target/"$env_target"/release/
if is_windows "$env_target"; then
### If our target is windows, we can simply zip our executable
### since having tar is not the norm and neither the default
zip -r "navi.zip" "navi.exe"
### We export a CI/CD variable to be used later in the pipeline
echo "EXTENSION=zip" >> $GITHUB_OUTPUT
else
### @alexis-opolka - I'm currently disabling the usage of UPX since I cannot find how
### it was used before the merge of the code from the @denisidoro/dotfiles repository.
###
#if upx --best --lzma "navi"; then
# log::info "upx succeeded"
#else
# log::info "upx failed"
#fi
### For all other targets, they have tar as the norm
### or have it installed by default.
tar -czf "navi.tar.gz" "navi"
### We export a CI/CD variable to be used later in the pipeline
echo "EXTENSION=tar.gz" >> $GITHUB_OUTPUT
fi
}
### --------------------------------------------------------------------------------------------------------------------
### Main script
### --------------------------------------------------------------------------------------------------------------------
release "$@"
================================================
FILE: scripts/test
================================================
#!/usr/bin/env bash
set -euo pipefail
export NAVI_HOME="$(cd "$(dirname "$0")/.." && pwd)"
source "${NAVI_HOME}/scripts/install"
"${NAVI_HOME}/tests/run"
================================================
FILE: shell/navi.plugin.bash
================================================
#!/usr/bin/env bash
_navi_call() {
local result="$(navi "$@" </dev/tty)"
printf "%s" "$result"
}
_navi_widget() {
local -r input="${READLINE_LINE}"
local -r last_command="$(echo "${input}" | navi fn widget::last_command)"
if [ -z "${last_command}" ]; then
local -r output="$(_navi_call --print)"
else
local -r find="${last_command}_NAVIEND"
local -r replacement="$(_navi_call --print --query "$last_command")"
local output="$input"
if [ -n "$replacement" ]; then
output="${input}_NAVIEND"
output=$(
shopt -u patsub_replacement
printf '%s' "${output//"$find"/"$replacement"}"
)
fi
fi
READLINE_LINE="$output"
READLINE_POINT=${#READLINE_LINE}
}
_navi_widget_legacy() {
_navi_call --print
}
if [ ${BASH_VERSION:0:1} -lt 4 ]; then
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"'
else
bind -x '"\C-g": _navi_widget'
fi
================================================
FILE: shell/navi.plugin.elv
================================================
use str
fn call-navi {
if (eq $edit:current-command '') {
var answer = (navi --print)
edit:replace-input $answer
} elif (not (str:contains-any $edit:current-command '|')) {
var answer = (navi --print --query $edit:current-command)
if (not-eq $answer '') {
edit:replace-input $answer
}
} else {
var @cmds query = (str:split '|' $edit:current-command)
var answer = (
if (eq $query '') {
navi --print
} else {
navi --print --query $query
}
)
if (not-eq $answer '') {
set cmds = [$@cmds $answer]
edit:replace-input (str:join '| ' $cmds)
}
}
}
set edit:insert:binding[Alt-h] = { call-navi >/dev/tty 2>&1 }
================================================
FILE: shell/navi.plugin.fish
================================================
function _navi_smart_replace
set --local query (commandline --current-process | string trim)
set --local version_parts ""
if test -n "$version"
set version_parts (string split '.' $version)
else
set version_parts (string split '.' (string match -r '\d+\.\d+\.\d+' (fish --version)))
end
set --local force_repaint false
# https://github.com/fish-shell/fish-shell/blob/d663f553dffba460d6d0bcdf93df21bda9ec6f3f/doc_src/interactive.rst?plain=1#L440
# > Bindings that change the mode are supposed to call the repaint-mode bind function
#
# Related issues
# - https://github.com/fish-shell/fish-shell/issues/5033
# - https://github.com/fish-shell/fish-shell/issues/5860
# - https://github.com/fish-shell/fish-shell/blob/d663f553dffba460d6d0bcdf93df21bda9ec6f3f/src/screen.rs#L531
#
# Introduced with: https://github.com/denisidoro/navi/pull/982
if test $version_parts[1] -ge 4
set force_repaint true
end
if test -n "$query"
set --local best_match (navi --print --query "$query" --best-match)
if test -n "$best_match"
# --replace without --current-process: --current-process treats newlines as process
# boundaries and flattens multi-line snippets into a single line
commandline --replace -- "$best_match"
commandline --function end-of-line
end
end
if test -z "$best_match"
set --local candidate (navi --print --query "$query")
if test -n "$candidate"
commandline --replace -- "$candidate"
commandline --function end-of-line
end
end
# always repaint to restore the prompt after fzf clobbers the terminal
if test "$force_repaint" = true
commandline --function repaint
end
end
bind \cg _navi_smart_replace
bind --mode insert \cg _navi_smart_replace
================================================
FILE: shell/navi.plugin.nu
================================================
export def navi_widget [] {
let current_input = (commandline)
let last_command = ($current_input | navi fn widget::last_command | str trim)
match ($last_command | is-empty) {
true => {^navi --print | complete | get "stdout"}
false => {
let find = $"($last_command)_NAVIEND"
let replacement = (^navi --print --query $'($last_command)' | complete | get "stdout")
match ($replacement | str trim | is-empty) {
false => {$"($current_input)_NAVIEND" | str replace $find $replacement}
true => $current_input
}
}
}
| str trim
| commandline edit --replace $in
commandline set-cursor --end
}
let nav_keybinding = {
name: "navi",
modifier: control,
keycode: char_g,
mode: [emacs, vi_normal, vi_insert],
event: {
send: executehostcommand,
cmd: navi_widget,
}
}
$env.config.keybindings = ($env.config.keybindings | append $nav_keybinding)
================================================
FILE: shell/navi.plugin.ps1
================================================
$null = New-Module {
function Invoke-Navi {
$startArgs = @{
FileName = "navi";
Arguments = $args;
RedirectStandardOutput = $true;
WorkingDirectory = $PWD;
UseShellExecute = $false;
}
$p = [System.Diagnostics.Process]@{StartInfo = $startArgs}
[void]$p.Start()
$result = $p.StandardOutput.ReadToEnd()
$p.WaitForExit()
$result
}
### Initial code from @lurebat (https://github.com/lurebat/)
### See #570 (https://github.com/denisidoro/navi/issues/570) for its original contribution
function Invoke-NaviWidget {
$ast = $tokens = $errors = $cursor = $null
[Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref] $ast, [ref] $tokens, [ref] $errors, [ref] $cursor)
$line = $ast.ToString().Trim()
$output = $null
if ([String]::IsNullOrEmpty($line)) {
$output = (Invoke-Navi "--print" | Out-String).Trim()
}
else {
$best_match = (Invoke-Navi "--print --best-match --query `"$line`"" | Out-String).Trim()
if ([String]::IsNullOrEmpty($best_match)) {
$output = (Invoke-Navi "--print --query `"$line`"" | Out-String).Trim()
}
else {
$output = $best_match
}
}
[Microsoft.PowerShell.PSConsoleReadLine]::RevertLine()
[Microsoft.PowerShell.PSConsoleReadLine]::InvokePrompt()
### Handling the case when the user escapes without selecting any entry
if (-Not([String]::IsNullOrEmpty($output))) {
[Microsoft.PowerShell.PSConsoleReadLine]::Insert([String]$output)
}
}
Set-PSReadlineKeyHandler -BriefDescription "A keybinding to open Navi Widget" -Chord Ctrl+g -ScriptBlock { Invoke-NaviWidget }
Export-ModuleMember -Function @()
}
================================================
FILE: shell/navi.plugin.zsh
================================================
#!/usr/bin/env zsh
_navi_call() {
local result="$(navi "$@" </dev/tty)"
printf "%s" "$result"
}
_navi_widget() {
local -r input="${LBUFFER}"
local -r last_command="$(echo "${input}" | navi fn widget::last_command)"
local replacement="$last_command"
if [ -z "$last_command" ]; then
replacement="$(_navi_call --print)"
elif [ "$LASTWIDGET" = "_navi_widget" ] && [ "$input" = "$previous_output" ]; then
replacement="$(_navi_call --print --query "$last_command")"
else
replacement="$(_navi_call --print --best-match --query "$last_command")"
fi
if [ -n "$replacement" ]; then
local -r find="${last_command}_NAVIEND"
previous_output="${input}_NAVIEND"
previous_output="${previous_output//$find/$replacement}"
else
previous_output="$input"
fi
zle kill-whole-line
LBUFFER="${previous_output}"
region_highlight=("P0 100 bold")
zle redisplay
}
zle -N _navi_widget
bindkey '^g' _navi_widget
================================================
FILE: src/bin/main.rs
================================================
extern crate navi;
use crate::navi::prelude::*;
use thiserror::Error;
#[derive(Error, Debug)]
#[error(
"\rHey, listen! navi encountered a problem.
Do you think this is a bug? File an issue at https://github.com/denisidoro/navi."
)]
pub struct FileAnIssue {
#[source]
source: anyhow::Error,
}
impl FileAnIssue {
pub fn new<SourceError>(source: SourceError) -> Self
where
SourceError: Into<anyhow::Error>,
{
FileAnIssue {
source: source.into(),
}
}
}
fn main() -> anyhow::Result<()> {
if let Err(err) = init_logger() {
// may need redir stderr to a file to show this log initialization error
eprintln!("failed to initialize logging: {err:?}");
}
navi::handle().map_err(|e| {
error!("{e:?}");
FileAnIssue::new(e).into()
})
}
fn init_logger() -> anyhow::Result<()> {
const FILE_NAME: &str = "navi.log";
let mut file = navi::default_config_pathbuf()?;
file.set_file_name(FILE_NAME);
// If config path doesn't exist, navi won't log.
if file.parent().map(|p| !p.exists()).unwrap_or(true) {
return Ok(());
}
let writer = std::fs::File::create(&file).with_context(|| format!("{file:?} is not created"))?;
tracing::subscriber::set_global_default(
tracing_subscriber::fmt()
.with_ansi(false)
.with_writer(writer)
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
.finish(),
)?;
debug!("tracing initialized");
Ok(())
}
================================================
FILE: src/clients/cheatsh.rs
================================================
use crate::prelude::*;
use std::process::Command;
fn map_line(line: &str) -> String {
line.trim().trim_end_matches(':').to_string()
}
fn as_lines(query: &str, markdown: &str) -> Vec<String> {
format!(
"% {query}, cheat.sh
{markdown}"
)
.lines()
.map(map_line)
.collect()
}
pub fn call(query: &str) -> Result<Vec<String>> {
let args = ["-qO-", &format!("cheat.sh/{query}")];
let child = Command::new("wget")
.args(args)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn();
let child = match child {
Ok(x) => x,
Err(_) => {
let msg = "navi was unable to call wget.
Make sure wget is correctly installed.";
return Err(anyhow!(msg));
}
};
let out = child.wait_with_output().context("Failed to wait for wget")?;
if let Some(0) = out.status.code() {
let stdout = out.stdout;
let plain_bytes = strip_ansi_escapes::strip(stdout);
let markdown = String::from_utf8(plain_bytes).context("Output is invalid utf8")?;
if markdown.starts_with("Unknown topic.") {
let msg = format!(
"`{}` not found in cheatsh.
Output:
{}
",
&query, markdown,
);
return Err(anyhow!(msg));
}
let lines = as_lines(query, &markdown);
Ok(lines)
} else {
let msg = format!(
"Failed to call:
wget {}
Output:
{}
Error:
{}
",
args.join(" "),
String::from_utf8(out.stdout).unwrap_or_else(|_e| "Unable to get output message".to_string()),
String::from_utf8(out.stderr).unwrap_or_else(|_e| "Unable to get error message".to_string())
);
Err(anyhow!(msg))
}
}
================================================
FILE: src/clients/mod.rs
================================================
pub mod cheatsh;
pub mod tldr;
================================================
FILE: src/clients/tldr.rs
================================================
use crate::config::CONFIG;
use crate::prelude::*;
use std::process::{Command, Stdio};
lazy_static! {
pub static ref VAR_TLDR_REGEX: Regex = Regex::new(r"\{\{(.*?)\}\}").expect("Invalid regex");
pub static ref NON_VAR_CHARS_REGEX: Regex = Regex::new(r"[^\da-zA-Z_]").expect("Invalid regex");
}
static VERSION_DISCLAIMER: &str =
"tldr-c-client (the default one in Homebrew) doesn't support markdown files, so navi can't use it.
The recommended client is tealdeer(https://github.com/dbrgn/tealdeer).";
fn convert_tldr_vars(line: &str) -> String {
let caps = VAR_TLDR_REGEX.find_iter(line);
let mut new_line: String = line.to_string();
for cap in caps {
let braced_var = cap.as_str();
let var = &braced_var[2..braced_var.len() - 2];
let mut new_var = NON_VAR_CHARS_REGEX.replace_all(var, "_").to_string();
if let Some(c) = new_var.chars().next() {
if c.to_string().parse::<u8>().is_ok() {
new_var = format!("example_{new_var}");
}
}
let bracketed_var = format!("<{new_var}>");
new_line = new_line.replace(braced_var, &bracketed_var);
}
new_line
}
fn convert_tldr(line: &str) -> String {
let line = line.trim();
if line.starts_with('-') {
format!("{}{}", "# ", &line[2..line.len() - 1])
} else if line.starts_with('`') {
convert_tldr_vars(&line[1..line.len() - 1])
} else if line.starts_with('%') {
line.to_string()
} else {
"".to_string()
}
}
fn markdown_lines(query: &str, markdown: &str) -> Vec<String> {
format!(
"% {query}, tldr
{markdown}"
)
.lines()
.map(convert_tldr)
.collect()
}
pub fn call(query: &str) -> Result<Vec<String>> {
let tealdeer = CONFIG.tealdeer();
let output_flag = if tealdeer { "--raw" } else { "--markdown" };
let args = [query, output_flag];
let child = Command::new("tldr")
.args(args)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn();
let child = match child {
Ok(x) => x,
Err(_) => {
let msg = format!(
"navi was unable to call tldr.
Make sure tldr is correctly installed.
Note:
{VERSION_DISCLAIMER}
"
);
return Err(anyhow!(msg));
}
};
let out = child.wait_with_output().context("Failed to wait for tldr")?;
if let Some(0) = out.status.code() {
let stdout = out.stdout;
let markdown = String::from_utf8(stdout).context("Output is invalid utf8")?;
let lines = markdown_lines(query, &markdown);
Ok(lines)
} else {
let msg = format!(
"Failed to call:
tldr {}
Output:
{}
Error:
{}
Note:
The client.tealdeer config option can be set to enable tealdeer support.
If you want to use another client, please make sure it supports the --markdown flag.
If you are already using a supported version you can ignore this message.
{}
",
args.join(" "),
String::from_utf8(out.stdout).unwrap_or_else(|_e| "Unable to get output message".to_string()),
String::from_utf8(out.stderr).unwrap_or_else(|_e| "Unable to get error message".to_string()),
VERSION_DISCLAIMER,
);
Err(anyhow!(msg))
}
}
================================================
FILE: src/commands/core/actor.rs
================================================
use crate::common::clipboard;
use crate::common::fs;
use crate::common::shell;
use crate::common::shell::ShellSpawnError;
use crate::config::Action;
use crate::deser;
use crate::env_var;
use crate::finder::structures::{Opts as FinderOpts, SuggestionType};
use crate::prelude::*;
use crate::structures::cheat::{Suggestion, VariableMap};
use crate::structures::item::Item;
use shell::EOF;
use std::process::Stdio;
fn prompt_finder(
variable_name: &str,
suggestion: Option<&Suggestion>,
variable_count: usize,
) -> Result<String> {
env_var::remove(env_var::PREVIEW_COLUMN);
env_var::remove(env_var::PREVIEW_DELIMITER);
env_var::remove(env_var::PREVIEW_MAP);
let mut extra_preview: Option<String> = None;
let (suggestions, initial_opts) = if let Some(s) = suggestion {
let (suggestion_command, suggestion_opts) = s;
if let Some(sopts) = suggestion_opts {
if let Some(c) = &sopts.column {
env_var::set(env_var::PREVIEW_COLUMN, c.to_string());
}
if let Some(d) = &sopts.delimiter {
env_var::set(env_var::PREVIEW_DELIMITER, d);
}
if let Some(m) = &sopts.map {
env_var::set(env_var::PREVIEW_MAP, m);
}
if let Some(p) = &sopts.preview {
extra_preview = Some(p.into());
}
}
let mut cmd = shell::out();
cmd.stdout(Stdio::piped()).arg(suggestion_command);
debug!(cmd = ?cmd);
let child = cmd
.spawn()
.map_err(|e| ShellSpawnError::new(suggestion_command, e))?;
let text = String::from_utf8(
child
.wait_with_output()
.context("Failed to wait and collect output from bash")?
.stdout,
)
.context("Suggestions are invalid utf8")?;
(text, suggestion_opts)
} else {
('\n'.to_string(), &None)
};
let exe = fs::exe_string();
let preview = if CONFIG.shell().contains("powershell") || CONFIG.shell().contains("pwsh") {
format!(
r#"{exe} preview-var {{+}} "{{q}}" "{name}"; {extra}"#,
exe = exe,
name = variable_name,
extra = extra_preview
.clone()
.map(|e| format!(" echo; {e}"))
.unwrap_or_default(),
)
} else if CONFIG.shell().contains("cmd.exe") {
format!(
r#"(@echo.{{+}}{eof}{{q}}{eof}{name}{eof}{extra}) | {exe} preview-var-stdin"#,
exe = exe,
name = variable_name,
extra = extra_preview.clone().unwrap_or_default(),
eof = EOF,
)
} else if CONFIG.shell().contains("fish") {
format!(
r#"{exe} preview-var "{{+}}" "{{q}}" "{name}"; {extra}"#,
exe = exe,
name = variable_name,
extra = extra_preview
.clone()
.map(|e| format!(" echo; {e}"))
.unwrap_or_default(),
)
} else {
format!(
r#"{exe} preview-var "$(cat <<{eof}
{{+}}
{eof}
)" "$(cat <<{eof}
{{q}}
{eof}
)" "{name}"; {extra}"#,
exe = exe,
name = variable_name,
extra = extra_preview
.clone()
.map(|e| format!(" echo; {e}"))
.unwrap_or_default(),
eof = EOF,
)
};
let mut opts = FinderOpts {
preview: Some(preview),
show_all_columns: true,
..initial_opts.clone().unwrap_or_else(FinderOpts::var_default)
};
opts.query = env_var::get(format!("{variable_name}__query")).ok();
if let Ok(f) = env_var::get(format!("{variable_name}__best")) {
opts.filter = Some(f);
opts.suggestion_type = SuggestionType::SingleSelection;
}
if opts.preview_window.is_none() {
opts.preview_window = Some(if extra_preview.is_none() {
format!("up:{}", variable_count + 3)
} else {
"right:50%".to_string()
});
}
if suggestion.is_none() {
opts.suggestion_type = SuggestionType::Disabled;
};
let (output, _) = CONFIG
.finder()
.call(opts, |stdin| {
stdin
.write_all(suggestions.as_bytes())
.context("Could not write to finder's stdin")?;
Ok(())
})
.context("finder was unable to prompt with suggestions")?;
Ok(output)
}
fn unique_result_count(results: &[&str]) -> usize {
let mut vars = results.to_owned();
vars.sort_unstable();
vars.dedup();
vars.len()
}
fn replace_variables_from_snippet(snippet: &str, tags: &str, variables: VariableMap) -> Result<String> {
let mut interpolated_snippet = String::from(snippet);
if CONFIG.prevent_interpolation() {
return Ok(interpolated_snippet);
}
let variables_found: Vec<&str> = deser::VAR_REGEX.find_iter(snippet).map(|m| m.as_str()).collect();
let variable_count = unique_result_count(&variables_found);
for bracketed_variable_name in variables_found {
let variable_name = &bracketed_variable_name[1..bracketed_variable_name.len() - 1];
let env_variable_name = env_var::escape(variable_name);
let env_value = env_var::get(&env_variable_name);
let value = if let Ok(e) = env_value {
e
} else if let Some(suggestion) = variables.get_suggestion(tags, variable_name) {
let mut new_suggestion = suggestion.clone();
new_suggestion.0 = replace_variables_from_snippet(&new_suggestion.0, tags, variables.clone())?;
prompt_finder(variable_name, Some(&new_suggestion), variable_count)?
} else {
prompt_finder(variable_name, None, variable_count)?
};
env_var::set(env_variable_name, &value);
interpolated_snippet = if value.as_str() == "\n" {
interpolated_snippet.replacen(bracketed_variable_name, "", 1)
} else {
interpolated_snippet.replacen(bracketed_variable_name, value.as_str(), 1)
};
}
Ok(interpolated_snippet)
}
pub fn with_absolute_path(snippet: String) -> String {
if let Some(s) = snippet.strip_prefix("navi ") {
return format!("{} {}", fs::exe_string(), s);
}
snippet
}
pub fn act(
extractions: Result<(&str, Item)>,
files: Vec<String>,
variables: Option<VariableMap>,
) -> Result<()> {
let (
key,
Item {
tags,
comment,
snippet,
file_index,
..
},
) = extractions.unwrap();
if key == "ctrl-o" {
edit::edit_file(Path::new(&files[file_index.expect("No files found")]))
.expect("Could not open file in external editor");
return Ok(());
}
env_var::set(env_var::PREVIEW_INITIAL_SNIPPET, &snippet);
env_var::set(env_var::PREVIEW_TAGS, &tags);
env_var::set(env_var::PREVIEW_COMMENT, comment);
let interpolated_snippet = {
let mut s = replace_variables_from_snippet(
&snippet,
&tags,
variables.expect("No variables received from finder"),
)
.context("Failed to replace variables from snippet")?;
s = with_absolute_path(s);
s = deser::with_new_lines(s);
s
};
match CONFIG.action() {
Action::Print => {
println!("{interpolated_snippet}");
}
Action::Execute => match key {
"ctrl-y" => {
clipboard::copy(interpolated_snippet)?;
}
_ => {
let mut cmd = shell::out();
cmd.arg(&interpolated_snippet[..]);
debug!(cmd = ?cmd);
cmd.spawn()
.map_err(|e| ShellSpawnError::new(&interpolated_snippet[..], e))?
.wait()
.context("bash was not running")?;
}
},
};
Ok(())
}
================================================
FILE: src/commands/core/mod.rs
================================================
mod actor;
use crate::clients::{cheatsh, tldr};
use crate::config::Source;
use crate::deser;
use crate::filesystem;
use crate::finder::structures::Opts as FinderOpts;
use crate::parser::Parser;
use crate::prelude::*;
use crate::structures::fetcher::{Fetcher, StaticFetcher};
use crate::welcome;
pub fn init(fetcher: Box<dyn Fetcher>) -> Result<()> {
let config = &CONFIG;
let opts = FinderOpts::snippet_default();
debug!("opts = {opts:#?}");
// let fetcher = config.fetcher();
let (raw_selection, (variables, files)) = config
.finder()
.call(opts, |writer| {
let mut parser = Parser::new(writer, true);
let found_something = fetcher
.fetch(&mut parser)
.context("Failed to parse variables intended for finder")?;
if !found_something {
welcome::populate_cheatsheet(&mut parser)?;
}
Ok((Some(parser.variables), fetcher.files()))
})
.context("Failed getting selection and variables from finder")?;
debug!(raw_selection = ?raw_selection);
let extractions = deser::terminal::read(&raw_selection, config.best_match());
if extractions.is_err() {
return init(fetcher);
}
actor::act(extractions, files, variables)?;
Ok(())
}
pub fn get_fetcher() -> Result<Box<dyn Fetcher>> {
let source = CONFIG.source();
debug!(source = ?source);
match source {
Source::Cheats(query) => {
let lines = cheatsh::call(&query)?;
let fetcher = Box::new(StaticFetcher::new(lines));
Ok(fetcher)
}
Source::Tldr(query) => {
let lines = tldr::call(&query)?;
let fetcher = Box::new(StaticFetcher::new(lines));
Ok(fetcher)
}
Source::Filesystem(path) => {
let fetcher = Box::new(filesystem::Fetcher::new(path));
Ok(fetcher)
}
Source::Welcome => {
let fetcher = Box::new(welcome::Fetcher::new());
Ok(fetcher)
}
}
}
pub fn main() -> Result<()> {
let fetcher = get_fetcher()?;
init(fetcher)
}
================================================
FILE: src/commands/func/map.rs
================================================
use crate::common::shell::{self, ShellSpawnError};
use crate::prelude::*;
pub fn expand() -> Result<()> {
let cmd = r#"sed -e 's/^.*$/"&"/' | tr '\n' ' '"#;
shell::out()
.arg(cmd)
.spawn()
.map_err(|e| ShellSpawnError::new(cmd, e))?
.wait()?;
Ok(())
}
================================================
FILE: src/commands/func/mod.rs
================================================
mod map;
mod widget;
use super::core;
use super::temp;
use crate::common::url;
use crate::prelude::*;
use clap::Args;
use clap::ValueEnum;
#[derive(Debug, Clone, ValueEnum)]
pub enum Func {
#[value(name = "url::open")]
UrlOpen,
#[value(name = "welcome")]
Welcome,
#[value(name = "widget::last_command")]
WidgetLastCommand,
#[value(name = "map::expand")]
MapExpand,
#[value(name = "temp")]
Temp,
}
#[derive(Debug, Clone, Args)]
pub struct Input {
/// Function name (example: "url::open")
#[arg(ignore_case = true)]
pub func: Func,
/// List of arguments (example: "https://google.com")
pub args: Vec<String>,
}
impl Runnable for Input {
fn run(&self) -> Result<()> {
let func = &self.func;
let args = self.args.clone(); // TODO
match func {
Func::UrlOpen => url::open(args),
Func::Welcome => core::main(),
Func::WidgetLastCommand => widget::last_command(),
Func::MapExpand => map::expand(),
Func::Temp => temp::main(),
}
}
}
================================================
FILE: src/commands/func/widget.rs
================================================
use crate::prelude::*;
use std::io::{self, Read};
pub fn last_command() -> Result<()> {
let mut text = String::new();
io::stdin().read_to_string(&mut text)?;
let replacements = vec![("||", "ග"), ("|", "ඛ"), ("&&", "ඝ")];
let parts = shellwords::split(&text).unwrap_or_else(|_| text.split('|').map(|s| s.to_string()).collect());
for p in parts {
for (pattern, escaped) in replacements.clone() {
if p.contains(pattern) && p != pattern && p != format!("{pattern}{pattern}") {
let replacement = p.replace(pattern, escaped);
text = text.replace(&p, &replacement);
}
}
}
let mut extracted = text.clone();
for (pattern, _) in replacements.clone() {
let mut new_parts = text.rsplit(pattern);
if let Some(extracted_attempt) = new_parts.next() {
if extracted_attempt.len() <= extracted.len() {
extracted = extracted_attempt.to_string();
}
}
}
for (pattern, escaped) in replacements.clone() {
text = text.replace(escaped, pattern);
extracted = extracted.replace(escaped, pattern);
}
println!("{}", extracted.trim_start());
Ok(())
}
================================================
FILE: src/commands/info.rs
================================================
use crate::filesystem;
use crate::prelude::*;
use clap::{Args, Subcommand};
#[derive(Debug, Clone, Args)]
pub struct Input {
#[clap(subcommand)]
pub info: Info,
}
#[derive(Debug, Clone, Subcommand)]
pub enum Info {
/// Prints a cheatsheet example.
CheatsExample,
/// Prints a configuration file example.
ConfigExample,
/// [DEPRECATED] Prints the default cheatsheets path.
/// Please use `info default-cheats-path` instead.
CheatsPath,
/// [DEPRECATED] Prints the default configuration path.
/// Please use `info default-config-path` instead.
ConfigPath,
/// Prints the default cheatsheets path.
DefaultCheatsPath,
/// Prints the default configuration path.
DefaultConfigPath,
}
impl Runnable for Input {
fn run(&self) -> Result<()> {
let info = &self.info;
match info {
// Here should be the example commands
Info::CheatsExample => {
println!("{}", include_str!("../../docs/examples/cheatsheet/example.cheat"))
}
Info::ConfigExample => println!(
"{}",
include_str!("../../docs/examples/configuration/config-example.yaml")
),
// Here should be the old deprecated default value commands
Info::CheatsPath => println!("{}", &filesystem::default_cheat_pathbuf()?.to_string()),
Info::ConfigPath => println!("{}", &filesystem::default_config_pathbuf()?.to_string()),
// Here should be the default values (computed at compile time)
Info::DefaultCheatsPath => println!("{}", &filesystem::default_cheat_pathbuf()?.to_string()),
Info::DefaultConfigPath => println!("{}", &filesystem::default_config_pathbuf()?.to_string()),
}
Ok(())
}
}
================================================
FILE: src/commands/mod.rs
================================================
pub mod core;
pub mod func;
pub mod info;
pub mod preview;
pub mod repo;
pub mod shell;
pub mod temp;
use crate::commands;
use crate::prelude::*;
pub fn handle() -> Result<()> {
use crate::config::Command::*;
debug!("CONFIG = {:#?}", &*CONFIG);
match CONFIG.cmd() {
None => commands::core::main(),
Some(c) => match c {
Preview(input) => input.run(),
PreviewVarStdin(input) => input.run(),
PreviewVar(input) => input.run(),
Widget(input) => input.run().context("Failed to print shell widget code"),
Fn(input) => input
.run()
.with_context(|| format!("Failed to execute function `{:#?}`", input.func)),
Info(input) => input
.run()
.with_context(|| format!("Failed to fetch info `{:#?}`", input.info)),
#[cfg(not(feature = "disable-repo-management"))]
Repo(input) => input.run(),
},
}
}
================================================
FILE: src/commands/preview/mod.rs
================================================
use crate::deser;
use crate::prelude::*;
use clap::Args;
use crossterm::style::{style, Stylize};
use std::process;
pub mod var;
pub mod var_stdin;
#[derive(Debug, Clone, Args)]
pub struct Input {
/// Selection line
pub line: String,
}
fn extract_elements(argstr: &str) -> Result<(&str, &str, &str)> {
let mut parts = argstr.split(deser::terminal::DELIMITER).skip(3);
let tags = parts.next().context("No `tags` element provided.")?;
let comment = parts.next().context("No `comment` element provided.")?;
let snippet = parts.next().context("No `snippet` element provided.")?;
Ok((tags, comment, snippet))
}
impl Runnable for Input {
fn run(&self) -> Result<()> {
let line = &self.line;
let (tags, comment, snippet) = extract_elements(line)?;
println!(
"{comment} {tags} \n{snippet}",
comment = style(comment).with(CONFIG.comment_color()),
tags = style(format!("[{tags}]")).with(CONFIG.tag_color()),
snippet = style(deser::fix_newlines(snippet)).with(CONFIG.snippet_color()),
);
process::exit(0)
}
}
================================================
FILE: src/commands/preview/var.rs
================================================
use crate::deser;
use crate::env_var;
use crate::finder;
use crate::prelude::*;
use clap::Args;
use crossterm::style::style;
use crossterm::style::Stylize;
use std::iter;
use std::process;
#[derive(Debug, Clone, Args)]
pub struct Input {
/// Selection line
pub selection: String,
/// Query match
pub query: String,
/// Typed text
pub variable: String,
}
impl Runnable for Input {
fn run(&self) -> Result<()> {
let selection = &self.selection;
let query = &self.query;
let variable = &self.variable;
let snippet = env_var::must_get(env_var::PREVIEW_INITIAL_SNIPPET);
let tags = env_var::must_get(env_var::PREVIEW_TAGS);
let comment = env_var::must_get(env_var::PREVIEW_COMMENT);
let column = env_var::parse(env_var::PREVIEW_COLUMN);
let delimiter = env_var::get(env_var::PREVIEW_DELIMITER).ok();
let map = env_var::get(env_var::PREVIEW_MAP).ok();
let active_color = CONFIG.tag_color();
let inactive_color = CONFIG.comment_color();
let mut colored_snippet = String::from(&snippet);
let mut visited_vars: HashSet<&str> = HashSet::new();
let mut variables = String::from("");
println!(
"{comment} {tags}",
comment = style(comment).with(CONFIG.comment_color()),
tags = style(format!("[{tags}]")).with(CONFIG.tag_color()),
);
let bracketed_current_variable = format!("<{variable}>");
let bracketed_variables: Vec<&str> = {
if snippet.contains(&bracketed_current_variable) {
deser::VAR_REGEX.find_iter(&snippet).map(|m| m.as_str()).collect()
} else {
iter::once(&bracketed_current_variable)
.map(|s| s.as_str())
.collect()
}
};
for bracketed_variable_name in bracketed_variables {
let variable_name = &bracketed_variable_name[1..bracketed_variable_name.len() - 1];
if visited_vars.contains(variable_name) {
continue;
} else {
visited_vars.insert(variable_name);
}
let is_current = variable_name == variable;
let variable_color = if is_current { active_color } else { inactive_color };
let env_variable_name = env_var::escape(variable_name);
let value = if is_current {
let v = selection.trim_matches('\'');
if v.is_empty() { query.trim_matches('\'') } else { v }.to_string()
} else if let Ok(v) = env_var::get(&env_variable_name) {
v
} else {
"".to_string()
};
let replacement = format!(
"{variable}",
variable = style(bracketed_variable_name).with(variable_color),
);
colored_snippet = colored_snippet.replace(bracketed_variable_name, &replacement);
variables = format!(
"{variables}\n{variable} = {value}",
variables = variables,
variable = style(variable_name).with(variable_color),
value = if env_var::get(&env_variable_name).is_ok() {
value
} else if is_current {
finder::process(value, column, delimiter.as_deref(), map.clone())
.expect("Unable to process value")
} else {
"".to_string()
}
);
}
println!("{snippet}", snippet = deser::fix_newlines(&colored_snippet));
println!("{variables}");
process::exit(0)
}
}
================================================
FILE: src/commands/preview/var_stdin.rs
================================================
use clap::Args;
use super::var;
use crate::common::shell::{self, ShellSpawnError, EOF};
use crate::prelude::*;
use std::io::{self, Read};
#[derive(Debug, Clone, Args)]
pub struct Input {}
impl Runnable for Input {
fn run(&self) -> Result<()> {
let mut text = String::new();
io::stdin().read_to_string(&mut text)?;
let mut parts = text.split(EOF);
let selection = parts.next().expect("Unable to get selection").to_owned();
let query = parts.next().expect("Unable to get query").to_owned();
let variable = parts.next().expect("Unable to get variable").trim().to_owned();
let input = var::Input {
selection,
query,
variable,
};
input.run()?;
if let Some(extra) = parts.next() {
if !extra.is_empty() {
print!("");
let mut cmd = shell::out();
cmd.arg(extra);
debug!(?cmd);
cmd.spawn().map_err(|e| ShellSpawnError::new(extra, e))?.wait()?;
}
}
Ok(())
}
}
================================================
FILE: src/commands/repo/add.rs
================================================
use crate::common::git;
use crate::filesystem;
use crate::finder::structures::{Opts as FinderOpts, SuggestionType};
use crate::finder::FinderChoice;
use crate::prelude::*;
use std::fs;
use std::path;
fn ask_if_should_import_all(finder: &FinderChoice) -> Result<bool> {
let opts = FinderOpts {
column: Some(1),
header: Some("Do you want to import all files from this repo?".to_string()),
..Default::default()
};
let (response, _) = finder
.call(opts, |stdin| {
stdin
.write_all(b"Yes\nNo")
.context("Unable to writer alternatives")?;
Ok(())
})
.context("Unable to get response")?;
Ok(response.to_lowercase().starts_with('y'))
}
pub fn main(uri: String) -> Result<()> {
let finder = CONFIG.finder();
let should_import_all = ask_if_should_import_all(&finder).unwrap_or(false);
let (actual_uri, user, repo) = git::meta(uri.as_str());
let cheat_pathbuf = filesystem::default_cheat_pathbuf()?;
let tmp_pathbuf = filesystem::tmp_pathbuf()?;
let tmp_path_str = &tmp_pathbuf.to_string();
let _ = filesystem::remove_dir(&tmp_pathbuf);
filesystem::create_dir(&tmp_pathbuf)?;
eprintln!("Cloning {} into {}...\n", &actual_uri, &tmp_path_str);
git::shallow_clone(actual_uri.as_str(), tmp_path_str)
.with_context(|| format!("Failed to clone `{actual_uri}`"))?;
let all_files = filesystem::all_cheat_files(&tmp_pathbuf).join("\n");
let opts = FinderOpts {
suggestion_type: SuggestionType::MultipleSelections,
preview: Some(format!("cat '{tmp_path_str}/{{}}'")),
header: Some("Select the cheatsheets you want to import with <TAB> then hit <Enter>\nUse Ctrl-R for (de)selecting all".to_string()),
preview_window: Some("right:30%".to_string()),
..Default::default()
};
let files = if should_import_all {
all_files
} else {
let (files, _) = finder
.call(opts, |stdin| {
stdin
.write_all(all_files.as_bytes())
.context("Unable to prompt cheats to import")?;
Ok(())
})
.context("Failed to get cheatsheet files from finder")?;
files
};
let to_folder = {
let mut p = cheat_pathbuf;
p.push(format!("{user}__{repo}"));
p
};
for file in files.split('\n') {
let from = {
let mut p = tmp_pathbuf.clone();
p.push(file);
p
};
let filename = file
.replace(&format!("{}{}", &tmp_path_str, path::MAIN_SEPARATOR), "")
.replace(path::MAIN_SEPARATOR, "__");
let to = {
let mut p = to_folder.clone();
p.push(filename);
p
};
fs::create_dir_all(&to_folder).unwrap_or(());
fs::copy(&from, &to)
.with_context(|| format!("Failed to copy `{}` to `{}`", &from.to_string(), &to.to_string()))?;
}
filesystem::remove_dir(&tmp_pathbuf)?;
eprintln!(
"The following .cheat files were imported successfully:\n{}\n\nThey are now located at {}",
files,
to_folder.to_string()
);
Ok(())
}
================================================
FILE: src/commands/repo/browse.rs
================================================
use crate::filesystem;
use crate::finder::structures::{Opts as FinderOpts, SuggestionType};
use crate::common::git;
use crate::prelude::*;
use std::fs;
pub fn main() -> Result<String> {
let finder = CONFIG.finder();
let repo_pathbuf = {
let mut p = filesystem::tmp_pathbuf()?;
p.push("featured");
p
};
let repo_path_str = &repo_pathbuf.to_string();
let _ = filesystem::remove_dir(&repo_pathbuf);
filesystem::create_dir(&repo_pathbuf)?;
let (repo_url, _, _) = git::meta("denisidoro/cheats");
git::shallow_clone(repo_url.as_str(), repo_path_str)
.with_context(|| format!("Failed to clone `{repo_url}`"))?;
let feature_repos_file = {
let mut p = repo_pathbuf.clone();
p.push("featured_repos.txt");
p
};
let repos = fs::read_to_string(feature_repos_file).context("Unable to fetch featured repositories")?;
let opts = FinderOpts {
column: Some(1),
suggestion_type: SuggestionType::SingleSelection,
..Default::default()
};
let (repo, _) = finder
.call(opts, |stdin| {
stdin
.write_all(repos.as_bytes())
.context("Unable to prompt featured repositories")?;
Ok(())
})
.context("Failed to get repo URL from finder")?;
filesystem::remove_dir(&repo_pathbuf)?;
Ok(repo)
}
================================================
FILE: src/commands/repo/mod.rs
================================================
use crate::commands;
use crate::prelude::*;
use clap::{Args, Subcommand};
pub mod add;
pub mod browse;
#[derive(Debug, Clone, Subcommand)]
pub enum RepoCommand {
/// Imports cheatsheets from a repo
Add {
/// A URI to a git repository containing .cheat files ("user/repo" will download cheats from github.com/user/repo)
uri: String,
},
/// Browses for featured cheatsheet repos
Browse,
}
#[derive(Debug, Clone, Args)]
pub struct Input {
#[clap(subcommand)]
pub cmd: RepoCommand,
}
impl Runnable for Input {
fn run(&self) -> Result<()> {
match &self.cmd {
RepoCommand::Add { uri } => {
add::main(uri.clone())
.with_context(|| format!("Failed to import cheatsheets from `{uri}`"))?;
commands::core::main()
}
RepoCommand::Browse => {
let repo = browse::main().context("Failed to browse featured cheatsheets")?;
add::main(repo.clone())
.with_context(|| format!("Failed to import cheatsheets from `{repo}`"))?;
commands::core::main()
}
}
}
}
================================================
FILE: src/commands/shell.rs
================================================
use std::fmt;
use std::fmt::Display;
use clap::Args;
use crate::common::shell::Shell;
use crate::prelude::*;
impl Display for Shell {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
Self::Bash => "bash",
Self::Zsh => "zsh",
Self::Fish => "fish",
Self::Elvish => "elvish",
Self::Nushell => "nushell",
Self::Powershell => "powershell",
};
write!(f, "{s}")
}
}
#[derive(Debug, Clone, Args)]
pub struct Input {
#[clap(ignore_case = true, default_value_t = Shell::Bash)]
pub shell: Shell,
}
impl Runnable for Input {
fn run(&self) -> Result<()> {
let shell = &self.shell;
let content = match shell {
Shell::Bash => include_str!("../../shell/navi.plugin.bash"),
Shell::Zsh => include_str!("../../shell/navi.plugin.zsh"),
Shell::Fish => include_str!("../../shell/navi.plugin.fish"),
Shell::Elvish => include_str!("../../shell/navi.plugin.elv"),
Shell::Nushell => include_str!("../../shell/navi.plugin.nu"),
Shell::Powershell => include_str!("../../shell/navi.plugin.ps1"),
};
println!("{content}");
Ok(())
}
}
================================================
FILE: src/commands/temp.rs
================================================
use crate::commands::core::get_fetcher;
use crate::common::shell::{self, ShellSpawnError};
use crate::finder::structures::Opts as FinderOpts;
use crate::parser::Parser;
use crate::{deser, prelude::*};
use std::io::{self, Write};
pub fn main() -> Result<()> {
let _config = &CONFIG;
let _opts = FinderOpts::snippet_default();
let fetcher = get_fetcher()?;
let hash: u64 = 2087294461664323320;
let mut buf = vec![];
let mut parser = Parser::new(&mut buf, false);
parser.set_hash(hash);
let _res = fetcher
.fetch(&mut parser)
.context("Failed to parse variables intended for finder")?;
let variables = parser.variables;
let item_str = String::from_utf8(buf)?;
let item = deser::raycast::read(&item_str)?;
dbg!(&item);
let x = variables.get_suggestion(&item.tags, "local_branch").expect("foo");
dbg!(&x);
let suggestion_command = x.0.clone();
let child = shell::out()
.stdout(Stdio::piped())
.arg(&suggestion_command)
.spawn()
.map_err(|e| ShellSpawnError::new(suggestion_command, e))?;
let text = String::from_utf8(
child
.wait_with_output()
.context("Failed to wait and collect output from bash")?
.stdout,
)
.context("Suggestions are invalid utf8")?;
dbg!(&text);
Ok(())
}
pub fn _main0() -> Result<()> {
let _config = &CONFIG;
let fetcher = get_fetcher()?;
let mut stdout = io::stdout();
let mut writer: Box<&mut dyn Write> = Box::new(&mut stdout);
let mut parser = Parser::new(&mut writer, false);
let _res = fetcher
.fetch(&mut parser)
.context("Failed to parse variables intended for finder")?;
Ok(())
}
================================================
FILE: src/common/clipboard.rs
================================================
use crate::common::shell::{self, ShellSpawnError, EOF};
use crate::prelude::*;
pub fn copy(text: String) -> Result<()> {
let cmd = r#"
exst() {
type "$1" &>/dev/null
}
_copy() {
if exst pbcopy; then
pbcopy
elif exst xclip; then
xclip -selection clipboard
elif exst clip.exe; then
clip.exe
else
exit 55
fi
}"#;
shell::out()
.arg(
format!(
r#"{cmd}
read -r -d '' x <<'{EOF}'
{text}
{EOF}
echo -n "$x" | _copy"#,
)
.as_str(),
)
.spawn()
.map_err(|e| ShellSpawnError::new(cmd, e))?
.wait()?;
Ok(())
}
================================================
FILE: src/common/deps.rs
================================================
use crate::prelude::*;
pub trait HasDeps {
fn deps(&self) -> HashSet<TypeId> {
HashSet::new()
}
}
================================================
FILE: src/common/fs.rs
================================================
use crate::prelude::*;
use remove_dir_all::remove_dir_all;
use std::ffi::OsStr;
use std::fs::{self, create_dir_all, File};
use std::io;
use thiserror::Error;
pub trait ToStringExt {
fn to_string(&self) -> String;
}
impl ToStringExt for Path {
fn to_string(&self) -> String {
self.to_string_lossy().to_string()
}
}
impl ToStringExt for OsStr {
fn to_string(&self) -> String {
self.to_string_lossy().to_string()
}
}
#[derive(Error, Debug)]
#[error("Invalid path `{0}`")]
pub struct InvalidPath(pub PathBuf);
#[derive(Error, Debug)]
#[error("Unable to read directory `{dir}`")]
pub struct UnreadableDir {
dir: PathBuf,
#[source]
source: anyhow::Error,
}
pub fn open(filename: &Path) -> Result<File> {
File::open(filename).with_context(|| {
let x = filename.to_string();
format!("Failed to open file {}", &x)
})
}
pub fn read_lines(filename: &Path) -> Result<impl Iterator<Item = Result<String>>> {
let file = open(filename)?;
Ok(io::BufReader::new(file)
.lines()
.map(|line| line.map_err(Error::from)))
}
pub fn pathbuf_to_string(pathbuf: &Path) -> Result<String> {
Ok(pathbuf
.as_os_str()
.to_str()
.ok_or_else(|| InvalidPath(pathbuf.to_path_buf()))
.map(str::to_string)?)
}
fn follow_symlink(pathbuf: PathBuf) -> Result<PathBuf> {
fs::read_link(pathbuf.clone())
.map(|o| {
let o_str = o
.as_os_str()
.to_str()
.ok_or_else(|| InvalidPath(o.to_path_buf()))?;
if o_str.starts_with('.') {
let p = pathbuf
.parent()
.ok_or_else(|| anyhow!("`{}` has no parent", pathbuf.display()))?;
let mut p = PathBuf::from(p);
p.push(o_str);
follow_symlink(p)
} else {
follow_symlink(o)
}
})
.unwrap_or(Ok(pathbuf))
}
fn exe_pathbuf() -> Result<PathBuf> {
let pathbuf = std::env::current_exe().context("Unable to acquire executable's path")?;
#[cfg(target_family = "windows")]
let pathbuf = dunce::canonicalize(pathbuf)?;
debug!(current_exe = ?pathbuf);
follow_symlink(pathbuf)
}
fn exe_abs_string() -> Result<String> {
pathbuf_to_string(&exe_pathbuf()?)
}
pub fn exe_string() -> String {
exe_abs_string().unwrap_or_else(|_| "navi".to_string())
}
pub fn create_dir(path: &Path) -> Result<()> {
create_dir_all(path).with_context(|| {
format!(
"Failed to create directory `{}`",
pathbuf_to_string(path).expect("Unable to parse {path}")
)
})
}
pub fn remove_dir(path: &Path) -> Result<()> {
remove_dir_all(path).with_context(|| {
format!(
"Failed to remove directory `{}`",
pathbuf_to_string(path).expect("Unable to parse {path}")
)
})
}
================================================
FILE: src/common/git.rs
================================================
use crate::common::shell::ShellSpawnError;
use crate::prelude::*;
use std::process::Command;
pub fn shallow_clone(uri: &str, target: &str) -> Result<()> {
Command::new("git")
.args(["clone", uri, target, "--depth", "1"])
.spawn()
.map_err(|e| ShellSpawnError::new("git clone", e))?
.wait()
.context("Unable to git clone")?;
Ok(())
}
pub fn meta(uri: &str) -> (String, String, String) {
let actual_uri = if uri.contains("://") || uri.contains('@') {
uri.to_string()
} else {
format!("https://github.com/{uri}")
};
let uri_to_split = actual_uri.replace(':', "/");
let parts: Vec<&str> = uri_to_split.split('/').collect();
let user = parts[parts.len() - 2];
let repo = parts[parts.len() - 1].replace(".git", "");
(actual_uri, user.to_string(), repo)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_meta_github_https() {
let (actual_uri, user, repo) = meta("https://github.com/denisidoro/navi");
assert_eq!(actual_uri, "https://github.com/denisidoro/navi".to_string());
assert_eq!(user, "denisidoro".to_string());
assert_eq!(repo, "navi".to_string());
}
#[test]
fn test_meta_github_ssh() {
let (actual_uri, user, repo) = meta("git@github.com:denisidoro/navi.git");
assert_eq!(actual_uri, "git@github.com:denisidoro/navi.git".to_string());
assert_eq!(user, "denisidoro".to_string());
assert_eq!(repo, "navi".to_string());
}
#[test]
fn test_meta_gitlab_https() {
let (actual_uri, user, repo) = meta("https://gitlab.com/user/repo.git");
assert_eq!(actual_uri, "https://gitlab.com/user/repo.git".to_string());
assert_eq!(user, "user".to_string());
assert_eq!(repo, "repo".to_string());
}
}
================================================
FILE: src/common/hash.rs
================================================
use std::hash::{Hash, Hasher};
const MAGIC_INIT: u64 = 0x811C_9DC5;
pub fn fnv<T: Hash>(x: &T) -> u64 {
let mut hasher = FnvHasher::new();
x.hash(&mut hasher);
hasher.finish()
}
struct FnvHasher(u64);
impl FnvHasher {
fn new() -> Self {
FnvHasher(MAGIC_INIT)
}
}
impl Hasher for FnvHasher {
fn finish(&self) -> u64 {
self.0
}
fn write(&mut self, bytes: &[u8]) {
for byte in bytes.iter() {
self.0 ^= u64::from(*byte);
self.0 = self.0.wrapping_mul(0x0100_0000_01b3);
}
}
}
================================================
FILE: src/common/mod.rs
================================================
pub mod clipboard;
pub mod deps;
pub mod fs;
pub mod git;
pub mod hash;
pub mod shell;
pub mod terminal;
pub mod url;
================================================
FILE: src/common/shell.rs
================================================
use crate::prelude::*;
use clap::ValueEnum;
use std::process::Command;
use thiserror::Error;
pub const EOF: &str = "NAVIEOF";
#[derive(Debug, Clone, ValueEnum)]
pub enum Shell {
Bash,
Zsh,
Fish,
Elvish,
Nushell,
Powershell,
}
#[derive(Error, Debug)]
#[error("Failed to spawn child process `bash` to execute `{command}`")]
pub struct ShellSpawnError {
command: String,
#[source]
source: anyhow::Error,
}
impl ShellSpawnError {
pub fn new<SourceError>(command: impl Into<String>, source: SourceError) -> Self
where
SourceError: std::error::Error + Sync + Send + 'static,
{
ShellSpawnError {
command: command.into(),
source: source.into(),
}
}
}
pub fn out() -> Command {
let words_str = CONFIG.shell();
let mut words_vec = shellwords::split(&words_str).expect("empty shell command");
let mut words = words_vec.iter_mut();
let first_cmd = words.next().expect("absent shell binary");
let mut cmd = Command::new(first_cmd);
cmd.args(words);
let dash_c = if words_str.contains("cmd.exe") { "/c" } else { "-c" };
cmd.arg(dash_c);
cmd
}
================================================
FILE: src/common/terminal.rs
================================================
use crate::prelude::*;
use crossterm::style;
use crossterm::terminal;
use std::process::Command;
const FALLBACK_WIDTH: u16 = 80;
fn width_with_shell_out() -> Result<u16> {
let output = if cfg!(target_os = "macos") {
Command::new("stty")
.arg("-f")
.arg("/dev/stderr")
.arg("size")
.stderr(Stdio::inherit())
.output()?
} else {
Command::new("stty")
.arg("size")
.arg("-F")
.arg("/dev/stderr")
.stderr(Stdio::inherit())
.output()?
};
if let Some(0) = output.status.code() {
let stdout = String::from_utf8(output.stdout).expect("Invalid utf8 output from stty");
let mut data = stdout.split_whitespace();
data.next();
return data
.next()
.expect("Not enough data")
.parse::<u16>()
.map_err(|_| anyhow!("Invalid width"));
}
Err(anyhow!("Invalid status code"))
}
pub fn width() -> u16 {
if let Ok((w, _)) = terminal::size() {
w
} else {
width_with_shell_out().unwrap_or(FALLBACK_WIDTH)
}
}
pub fn parse_ansi(ansi: &str) -> Option<style::Color> {
style::Color::parse_ansi(&format!("5;{ansi}"))
}
#[derive(Debug, Clone)]
pub struct Color(#[allow(unused)] pub style::Color); // suppress warning: field `0` is never read.
impl FromStr for Color {
type Err = &'static str;
fn from_str(ansi: &str) -> Result<Self, Self::Err> {
if let Some(c) = parse_ansi(ansi) {
Ok(Color(c))
} else {
Err("Invalid color")
}
}
}
================================================
FILE: src/common/url.rs
================================================
use crate::common::shell::{self, ShellSpawnError};
use crate::prelude::*;
use anyhow::Result;
use shell::EOF;
pub fn open(args: Vec<String>) -> Result<()> {
let url = args
.into_iter()
.next()
.ok_or_else(|| anyhow!("No URL specified"))?;
let code = r#"
exst() {
type "$1" &>/dev/null
}
_open_url() {
local -r url="$1"
if exst xdg-open; then
xdg-open "$url" &disown
elif exst open; then
echo "$url" | xargs -I% open "%"
else
exit 55
fi
}"#;
let cmd = format!(
r#"{code}
read -r -d '' url <<'{EOF}'
{url}
{EOF}
_open_url "$url""#,
);
shell::out()
.arg(cmd.as_str())
.spawn()
.map_err(|e| ShellSpawnError::new(cmd, e))?
.wait()?;
Ok(())
}
================================================
FILE: src/config/cli.rs
================================================
use crate::commands;
use crate::finder::FinderChoice;
use clap::{crate_version, Parser, Subcommand};
#[derive(Debug, Parser)]
#[command(after_help = "\x1b[0;33mMORE INFO:\x1b[0;0m
Please refer to \x1b[0;32mhttps://github.com/denisidoro/navi\x1b[0;0m
\x1b[0;33mENVIRONMENT VARIABLES:\x1b[0m
\x1b[0;32mNAVI_CONFIG\x1b[0;0m # path to config file
\x1b[0;32mNAVI_CONFIG_YAML\x1b[0;0m # config file content
\x1b[0;33mFEATURE STABILITY:\x1b[0m
\x1b[0;32mexperimental\x1b[0;0m # may be removed or changed at any time
\x1b[0;32mdeprecated\x1b[0;0m # may be removed in 3 months after first being deprecated
\x1b[0;33mCOMMON NAVI COMMANDS:\x1b[0m
Run \x1b[0;32mnavi fn welcome\x1b[0;0m to browse the cheatsheet for navi itself
\x1b[0;33mEXAMPLES:\x1b[0m
navi # default behavior
navi fn welcome # show cheatsheets for navi itself
navi --print # doesn't execute the snippet
navi --tldr docker # search for docker cheatsheets using tldr
navi --cheatsh docker # search for docker cheatsheets using cheatsh
navi --path '/some/dir:/other/dir' # use .cheat files from custom paths
navi --query git # filter results by \"git\"
navi --query 'create db' --best-match # autoselect the snippet that best matches a query
db=my navi --query 'create db' --best-match # same, but set the value for the <name> variable
navi repo add denisidoro/cheats # import cheats from a git repository
eval \"$(navi widget zsh)\" # load the zsh widget
navi --finder 'skim' # set skim as finder, instead of fzf
navi --fzf-overrides '--with-nth 1,2' # show only the comment and tag columns
navi --fzf-overrides '--no-select-1' # prevent autoselection in case of single line
navi --fzf-overrides-var '--no-select-1' # same, but for variable selection
navi --fzf-overrides '--nth 1,2' # only consider the first two columns for search
navi --fzf-overrides '--no-exact' # use looser search algorithm
navi --tag-rules='git,!checkout' # show non-checkout git snippets only")]
#[clap(version = crate_version!())]
pub(super) struct ClapConfig {
/// Colon-separated list of paths containing .cheat files
#[arg(short, long)]
pub path: Option<String>,
/// Instead of executing a snippet, prints it to stdout
#[arg(long)]
#[cfg(not(feature = "disable-command-execution"))]
pub print: bool,
/// Returns the best match
#[arg(long)]
pub best_match: bool,
/// Prevents variable interpolation
#[arg(long)]
pub prevent_interpolation: bool,
/// Searches for cheatsheets using the tldr-pages repository
#[arg(long)]
pub tldr: Option<String>,
/// [Experimental] Comma-separated list that acts as filter for tags. Parts starting with ! represent negation
#[arg(long)]
pub tag_rules: Option<String>,
/// Searches for cheatsheets using the cheat.sh repository
#[arg(long)]
pub cheatsh: Option<String>,
/// Prepopulates the search field
#[arg(short, long, allow_hyphen_values = true)]
pub query: Option<String>,
/// Finder overrides for snippet selection
#[arg(long, allow_hyphen_values = true)]
pub fzf_overrides: Option<String>,
/// Finder overrides for variable selection
#[arg(long, allow_hyphen_values = true)]
pub fzf_overrides_var: Option<String>,
/// Finder application to use
#[arg(long, ignore_case = true)]
pub finder: Option<FinderChoice>,
#[command(subcommand)]
pub cmd: Option<Command>,
}
impl ClapConfig {
pub fn new() -> Self {
Self::parse()
}
}
// #[derive(Subcommand, Debug, Clone, Runnable, HasDeps)]
#[derive(Subcommand, Debug, Clone)]
pub enum Command {
/// [Experimental] Calls internal functions
Fn(commands::func::Input),
/// Manages cheatsheet repositories
#[cfg(not(feature = "disable-repo-management"))]
Repo(commands::repo::Input),
/// Used for fzf's preview window when selecting snippets
#[command(hide = true)]
Preview(commands::preview::Input),
/// Used for fzf's preview window when selecting variable suggestions
#[command(hide = true)]
PreviewVar(commands::preview::var::Input),
/// Used for fzf's preview window when selecting variable suggestions
#[command(hide = true)]
PreviewVarStdin(commands::preview::var_stdin::Input),
/// Outputs shell widget source code
Widget(commands::shell::Input),
/// Shows info
Info(commands::info::Input),
}
#[derive(Debug)]
pub enum Source {
Filesystem(Option<String>),
Tldr(String),
Cheats(String),
Welcome,
}
pub enum Action {
Print,
Execute,
}
================================================
FILE: src/config/env.rs
================================================
use crate::env_var;
use crate::finder::FinderChoice;
use crate::prelude::*;
#[derive(Debug)]
pub struct EnvConfig {
pub config_yaml: Option<String>,
pub config_path: Option<String>,
pub path: Option<String>,
pub finder: Option<FinderChoice>,
pub fzf_overrides: Option<String>,
pub fzf_overrides_var: Option<String>,
}
impl EnvConfig {
pub fn new() -> Self {
Self {
config_yaml: env_var::get(env_var::CONFIG_YAML).ok(),
config_path: env_var::get(env_var::CONFIG).ok(),
path: env_var::get(env_var::PATH).ok(),
finder: env_var::get(env_var::FINDER)
.ok()
.and_then(|x| FinderChoice::from_str(&x).ok()),
fzf_overrides: env_var::get(env_var::FZF_OVERRIDES).ok(),
fzf_overrides_var: env_var::get(env_var::FZF_OVERRIDES_VAR).ok(),
}
}
}
================================================
FILE: src/config/mod.rs
================================================
mod cli;
mod env;
mod yaml;
use crate::commands::func::Func;
use crate::finder::FinderChoice;
use crate::prelude::debug;
pub use cli::*;
use crossterm::style::Color;
use env::EnvConfig;
use yaml::YamlConfig;
lazy_static! {
pub static ref CONFIG: Config = Config::new();
}
#[derive(Debug)]
pub struct Config {
yaml: YamlConfig,
clap: ClapConfig,
env: EnvConfig,
}
impl Config {
pub fn new() -> Self {
let env = EnvConfig::new();
let yaml = YamlConfig::get(&env).unwrap_or_else(|e| {
eprintln!("Error parsing config file: {e}");
eprintln!("Fallbacking to default one...");
eprintln!();
YamlConfig::default()
});
let clap = ClapConfig::new();
Self { yaml, clap, env }
}
pub fn best_match(&self) -> bool {
self.clap.best_match
}
pub fn prevent_interpolation(&self) -> bool {
self.clap.prevent_interpolation
}
pub fn cmd(&self) -> Option<&Command> {
self.clap.cmd.as_ref()
}
pub fn source(&self) -> Source {
if let Some(query) = self.clap.tldr.clone() {
Source::Tldr(query)
} else if let Some(query) = self.clap.cheatsh.clone() {
Source::Cheats(query)
} else if let Some(Command::Fn(input)) = self.cmd() {
if let Func::Welcome = input.func {
Source::Welcome
} else {
Source::Filesystem(self.path())
}
} else {
Source::Filesystem(self.path())
}
}
pub fn path(&self) -> Option<String> {
if self.clap.path.is_some() {
debug!("CLAP PATH: {}", self.clap.path.as_ref().unwrap());
}
self.clap
.path
.clone()
.or_else(|| {
if self.env.path.is_some() {
debug!("ENV PATH: {}", self.env.path.as_ref().unwrap());
}
self.env.path.clone()
})
.or_else(|| {
let p = self.yaml.cheats.paths.clone();
if p.is_empty() {
None
} else {
debug!("MULTIPLE YAML PATH: {}", p.as_slice().join(","));
Some(p.join(crate::filesystem::JOIN_SEPARATOR))
}
})
.or_else(|| {
if self.yaml.cheats.path.is_some() {
debug!(
"DEPRECATED UNIQUE YAML PATH: {}",
self.yaml.cheats.path.as_ref().unwrap()
);
}
self.yaml.cheats.path.clone()
})
.or_else(|| {
debug!("No specific path given!");
None
})
}
pub fn finder(&self) -> FinderChoice {
self.clap
.finder
.or(self.env.finder)
.unwrap_or(self.yaml.finder.command)
}
pub fn fzf_overrides(&self) -> Option<String> {
self.clap
.fzf_overrides
.clone()
.or_else(|| self.env.fzf_overrides.clone())
.or_else(|| self.yaml.finder.overrides.clone())
}
pub fn fzf_overrides_var(&self) -> Option<String> {
self.clap
.fzf_overrides_var
.clone()
.or_else(|| self.env.fzf_overrides_var.clone())
.or_else(|| self.yaml.finder.overrides_var.clone())
}
pub fn delimiter_var(&self) -> Option<String> {
self.yaml.finder.delimiter_var.clone()
}
pub fn tealdeer(&self) -> bool {
self.yaml.client.tealdeer
}
pub fn shell(&self) -> String {
self.yaml.shell.command.clone()
}
pub fn finder_shell(&self) -> String {
self.yaml
.shell
.finder_command
.clone()
.unwrap_or_else(|| self.yaml.shell.command.clone())
}
pub fn tag_rules(&self) -> Option<String> {
self.clap
.tag_rules
.clone()
.or_else(|| self.yaml.search.tags.clone())
}
pub fn tag_color(&self) -> Color {
self.yaml.style.tag.color.get()
}
pub fn comment_color(&self) -> Color {
self.yaml.style.comment.color.get()
}
pub fn snippet_color(&self) -> Color {
self.yaml.style.snippet.color.get()
}
pub fn tag_width_percentage(&self) -> u16 {
self.yaml.style.tag.width_percentage
}
pub fn comment_width_percentage(&self) -> u16 {
self.yaml.style.comment.width_percentage
}
pub fn snippet_width_percentage(&self) -> u16 {
self.yaml.style.snippet.width_percentage
}
pub fn tag_min_width(&self) -> u16 {
self.yaml.style.tag.min_width
}
pub fn comment_min_width(&self) -> u16 {
self.yaml.style.comment.min_width
}
pub fn snippet_min_width(&self) -> u16 {
self.yaml.style.snippet.min_width
}
#[cfg(feature = "disable-command-execution")]
fn print(&self) -> bool {
true
}
#[cfg(not(feature = "disable-command-execution"))]
fn print(&self) -> bool {
self.clap.print
}
pub fn action(&self) -> Action {
if self.print() {
Action::Print
} else {
Action::Execute
}
}
pub fn get_query(&self) -> Option<String> {
let q = self.clap.query.clone();
if q.is_some() {
return q;
}
if self.best_match() {
match self.source() {
Source::Tldr(q) => Some(q),
Source::Cheats(q) => Some(q),
_ => Some(String::from("")),
}
} else {
None
}
}
}
impl Default for Config {
fn default() -> Self {
Self::new()
}
}
================================================
FILE: src/config/yaml.rs
================================================
use super::env::EnvConfig;
use crate::common::fs;
use crate::filesystem::default_config_pathbuf;
use crate::finder::FinderChoice;
use crate::prelude::*;
use crossterm::style::Color as TerminalColor;
use serde::de;
#[derive(Deserialize, Debug)]
pub struct Color(#[serde(deserialize_with = "color_deserialize")] TerminalColor);
impl Color {
pub fn get(&self) -> TerminalColor {
self.0
}
}
fn color_deserialize<'de, D>(deserializer: D) -> Result<TerminalColor, D::Error>
where
D: de::Deserializer<'de>,
{
let s: String = Deserialize::deserialize(deserializer)?;
TerminalColor::try_from(s.as_str())
.map_err(|_| de::Error::custom(format!("Failed to deserialize color: {s}")))
}
#[derive(Deserialize, Debug)]
#[serde(default)]
pub struct ColorWidth {
pub color: Color,
pub width_percentage: u16,
pub min_width: u16,
}
#[derive(Deserialize, Debug)]
#[serde(default)]
pub struct Style {
pub tag: ColorWidth,
pub comment: ColorWidth,
pub snippet: ColorWidth,
}
#[derive(Deserialize, Debug)]
#[serde(default)]
pub struct Finder {
#[serde(deserialize_with = "finder_deserialize")]
pub command: FinderChoice,
pub overrides: Option<String>,
pub overrides_var: Option<String>,
pub delimiter_var: Option<String>,
}
fn finder_deserialize<'de, D>(deserializer: D) -> Result<FinderChoice, D::Error>
where
D: de::Deserializer<'de>,
{
let s: String = Deserialize::deserialize(deserializer)?;
FinderChoice::from_str(s.to_lowercase().as_str())
.map_err(|_| de::Error::custom(format!("Failed to deserialize finder: {s}")))
}
#[derive(Deserialize, Default, Debug)]
#[serde(default)]
pub struct Cheats {
pub path: Option<String>,
pub paths: Vec<String>,
}
#[derive(Deserialize, Default, Debug)]
#[serde(default)]
pub struct Search {
pub tags: Option<String>,
}
#[derive(Deserialize, Debug)]
#[serde(default)]
pub struct Shell {
pub command: String,
pub finder_command: Option<String>,
}
#[derive(Deserialize, Debug)]
#[serde(default)]
#[derive(Default)]
pub struct Client {
pub tealdeer: bool,
}
#[derive(Deserialize, Debug)]
#[serde(default)]
pub struct YamlConfig {
pub style: Style,
pub finder: Finder,
pub cheats: Cheats,
pub search: Search,
pub shell: Shell,
pub client: Client,
pub source: String, // <= The source of the current configuration
}
impl YamlConfig {
fn from_str(text: &str) -> Result<Self> {
serde_yaml::from_str(text).map_err(|e| e.into())
}
fn from_path(path: &Path) -> Result<Self> {
let file = fs::open(path)?;
let reader = BufReader::new(file);
serde_yaml::from_reader(reader).map_err(|e| e.into())
}
pub fn get(env: &EnvConfig) -> Result<YamlConfig> {
if let Some(yaml) = env.config_yaml.as_ref() {
// We're getting the configuration from the environment variable `NAVI_CONFIG_YAML`
let mut cfg = Self::from_str(yaml)?;
cfg.source = "ENV_NAVI_CONFIG_YAML".to_string();
return Ok(cfg);
}
if let Some(path_str) = env.config_path.as_ref() {
// We're getting the configuration from a file given in the environment variable 'NAVI_CONFIG'
let p = PathBuf::from(path_str);
let mut cfg = YamlConfig::from_path(&p)?;
cfg.source = "ENV_NAVI_CONFIG".to_string();
return Ok(cfg);
}
if let Ok(p) = default_config_pathbuf() {
// We're getting the configuration from the default path
if p.exists() {
let mut cfg = YamlConfig::from_path(&p)?;
cfg.source = "DEFAULT_CONFIG_FILE".to_string();
return Ok(cfg);
}
}
// As no configuration has been found, we set the YAML configuration
// to be its default (built-in) value.
Ok(YamlConfig::default())
}
}
impl Default for ColorWidth {
fn default() -> Self {
Self {
color: Color(TerminalColor::Blue),
width_percentage: 26,
min_width: 20,
}
}
}
impl Default for Style {
fn default() -> Self {
Self {
tag: ColorWidth {
color: Color(TerminalColor::Cyan),
width_percentage: 26,
min_width: 20,
},
comment: ColorWidth {
color: Color(TerminalColor::Blue),
width_percentage: 42,
min_width: 45,
},
snippet: Default::default(),
}
}
}
impl Default for Finder {
fn default() -> Self {
Self {
command: FinderChoice::Fzf,
overrides: None,
overrides_var: None,
delimiter_var: None,
}
}
}
impl Default for Shell {
fn default() -> Self {
Self {
command: "bash".to_string(),
finder_command: None,
}
}
}
impl Default for YamlConfig {
fn default() -> Self {
Self {
style: Default::default(),
finder: Default::default(),
cheats: Default::default(),
search: Default::default(),
shell: Default::default(),
client: Default::default(),
source: "BUILT-IN".to_string(),
}
}
}
================================================
FILE: src/deser/mod.rs
================================================
use crate::prelude::*;
use unicode_width::UnicodeWidthStr;
pub mod raycast;
pub mod terminal;
const NEWLINE_ESCAPE_CHAR: char = '\x15';
pub const LINE_SEPARATOR: &str = " \x15 ";
lazy_static! {
pub static ref NEWLINE_REGEX: Regex = Regex::new(r"\\\s+").expect("Invalid regex");
pub static ref VAR_REGEX: Regex = Regex::new(r"\\?<(\w[\w\d\-_]*)>").expect("Invalid regex");
}
pub fn with_new_lines(txt: String) -> String {
txt.replace(LINE_SEPARATOR, "\n")
}
pub fn fix_newlines(txt: &str) -> String {
if txt.contains(NEWLINE_ESCAPE_CHAR) {
(*NEWLINE_REGEX)
.replace_all(txt.replace(LINE_SEPARATOR, " ").as_str(), "")
.to_string()
} else {
txt.to_string()
}
}
fn limit_str(text: &str, length: usize) -> String {
let len = UnicodeWidthStr::width(text);
if len <= length {
format!("{}{}", text, " ".repeat(length - len))
} else {
let mut new_length = length;
let mut actual_length = 9999;
let mut txt = text.to_owned();
while actual_length >= length {
txt = txt.chars().take(new_length - 1).collect::<String>();
actual_length = UnicodeWidthStr::width(txt.as_str());
new_length -= 1;
}
format!("{}…{}", txt, " ".repeat(length - actual_length - 1))
}
}
================================================
FILE: src/deser/raycast.rs
================================================
use super::*;
use crate::structures::item::Item;
const FIELD_SEP_ESCAPE_CHAR: char = '\x16';
pub fn write(item: &Item) -> String {
format!(
"{hash}{delimiter}{tags}{delimiter}{comment}{delimiter}{icon}{delimiter}{snippet}\n",
hash = item.hash(),
tags = item.tags,
comment = item.comment,
delimiter = FIELD_SEP_ESCAPE_CHAR,
icon = item.icon.clone().unwrap_or_default(),
snippet = &item.snippet.trim_end_matches(LINE_SEPARATOR),
)
}
pub fn read(line: &str) -> Result<Item> {
let mut parts = line.split(FIELD_SEP_ESCAPE_CHAR);
let hash: u64 = parts
.next()
.context("no hash")?
.parse()
.context("hash not a u64")?;
let tags = parts.next().context("no tags")?.into();
let comment = parts.next().context("no comment")?.into();
let icon_str = parts.next().context("no icon")?;
let snippet = parts.next().context("no snippet")?.into();
let icon = if icon_str.is_empty() {
None
} else {
Some(icon_str.into())
};
let item = Item {
tags,
comment,
icon,
snippet,
..Default::default()
};
if item.hash() != hash {
dbg!(&item.hash());
dbg!(hash);
Err(anyhow!("Incorrect hash"))
} else {
Ok(item)
}
}
================================================
FILE: src/deser/terminal.rs
================================================
use super::*;
use crate::common::terminal;
use crate::structures::item::Item;
use crossterm::style::{style, Stylize};
use std::cmp::max;
pub fn get_widths() -> (usize, usize, usize) {
let width = terminal::width();
let tag_width_percentage = max(
CONFIG.tag_min_width(),
width * CONFIG.tag_width_percentage() / 100,
);
let comment_width_percentage = max(
CONFIG.comment_min_width(),
width * CONFIG.comment_width_percentage() / 100,
);
let snippet_width_percentage = max(
CONFIG.snippet_min_width(),
width * CONFIG.snippet_width_percentage() / 100,
);
(
usize::from(tag_width_percentage),
usize::from(comment_width_percentage),
usize::from(snippet_width_percentage),
)
}
pub const DELIMITER: &str = r" ⠀";
lazy_static! {
pub static ref COLUMN_WIDTHS: (usize, usize, usize) = get_widths();
}
pub fn write(item: &Item) -> String {
let (tag_width_percentage, comment_width_percentage, snippet_width_percentage) = *COLUMN_WIDTHS;
format!(
"{tags_short}{delimiter}{comment_short}{delimiter}{snippet_short}{delimiter}{tags}{delimiter}{comment}{delimiter}{snippet}{delimiter}{file_index}{delimiter}\n",
tags_short = style(limit_str(&item.tags, tag_width_percentage)).with(CONFIG.tag_color()),
comment_short = style(limit_str(&item.comment, comment_width_percentage)).with(CONFIG.comment_color()),
snippet_short = style(limit_str(&fix_newlines(&item.snippet), snippet_width_percentage)).with(CONFIG.snippet_color()),
tags = item.tags,
comment = item.comment,
delimiter = DELIMITER,
snippet = &item.snippet.trim_end_matches(LINE_SEPARATOR),
file_index = item.file_index.unwrap_or(0),
)
}
pub fn read(raw_snippet: &str, is_single: bool) -> Result<(&str, Item)> {
let mut lines = raw_snippet.split('\n');
let key = if is_single {
"enter"
} else {
lines
.next()
.context("Key was promised but not present in `selections`")?
};
let mut parts = lines
.next()
.context("No more parts in `selections`")?
.split(DELIMITER)
.skip(3);
let tags = parts.next().unwrap_or("").into();
let comment = parts.next().unwrap_or("").into();
let snippet = parts.next().unwrap_or("").into();
let file_index = parts.next().unwrap_or("").parse().ok();
let item = Item {
tags,
comment,
snippet,
file_index,
..Default::default()
};
Ok((key, item))
}
================================================
FILE: src/env_var.rs
================================================
use crate::prelude::*;
pub use env::remove_var as remove;
pub use env::set_var as set;
pub use env::var as get;
use std::env;
pub const PREVIEW_INITIAL_SNIPPET: &str = "NAVI_PREVIEW_INITIAL_SNIPPET";
pub const PREVIEW_TAGS: &str = "NAVI_PREVIEW_TAGS";
pub const PREVIEW_COMMENT: &str = "NAVI_PREVIEW_COMMENT";
pub const PREVIEW_COLUMN: &str = "NAVI_PREVIEW_COLUMN";
pub const PREVIEW_DELIMITER: &str = "NAVI_PREVIEW_DELIMITER";
pub const PREVIEW_MAP: &str = "NAVI_PREVIEW_MAP";
pub const PATH: &str = "NAVI_PATH";
pub const FZF_OVERRIDES: &str = "NAVI_FZF_OVERRIDES";
pub const FZF_OVERRIDES_VAR: &str = "NAVI_FZF_OVERRIDES_VAR";
pub const FINDER: &str = "NAVI_FINDER";
pub const CONFIG: &str = "NAVI_CONFIG";
pub const CONFIG_YAML: &str = "NAVI_CONFIG_YAML";
pub fn parse<T: FromStr>(varname: &str) -> Option<T> {
if let Ok(x) = env::var(varname) {
x.parse::<T>().ok()
} else {
None
}
}
pub fn must_get(name: &str) -> String {
if let Ok(v) = env::var(name) {
v
} else {
panic!("{name} not set")
}
}
pub fn escape(name: &str) -> String {
name.replace('-', "_")
}
================================================
FILE: src/filesystem.rs
================================================
pub use crate::common::fs::{create_dir, exe_string, read_lines, remove_dir};
use crate::env_var;
use crate::parser::Parser;
use crate::prelude::*;
use crate::structures::fetcher;
use etcetera::BaseStrategy;
use regex::Regex;
use std::cell::RefCell;
use std::path::MAIN_SEPARATOR;
use walkdir::WalkDir;
/// Multiple paths are joint by a platform-specific separator.
/// FIXME: it's actually incorrect to assume a path doesn't containing this separator
#[cfg(target_family = "windows")]
pub const JOIN_SEPARATOR: &str = ";";
#[cfg(not(target_family = "windows"))]
pub const JOIN_SEPARATOR: &str = ":";
pub fn all_cheat_files(path: &Path) -> Vec<String> {
WalkDir::new(path)
.follow_links(true)
.into_iter()
.filter_map(|e| e.ok())
.map(|e| e.path().to_str().unwrap_or("").to_string())
.filter(|e| e.ends_with(".cheat") || e.ends_with(".cheat.md"))
.collect::<Vec<String>>()
}
fn paths_from_path_param(env_var: &str) -> impl Iterator<Item = &str> {
env_var.split(JOIN_SEPARATOR).filter(|folder| folder != &"")
}
fn compiled_default_path(path: Option<&str>) -> Option<PathBuf> {
match path {
Some(path) => {
let path = if path.contains(MAIN_SEPARATOR) {
path.split(MAIN_SEPARATOR).next().unwrap()
} else {
path
};
let path = Path::new(path);
if path.exists() {
Some(path.to_path_buf())
} else {
None
}
}
None => None,
}
}
pub fn default_cheat_pathbuf() -> Result<PathBuf> {
let mut pathbuf = get_data_dir_by_platform()?;
pathbuf.push("navi");
pathbuf.push("cheats");
if pathbuf.exists() {
if let Some(path) = compiled_default_path(option_env!("NAVI_PATH")) {
pathbuf = path;
}
}
Ok(pathbuf)
}
pub fn default_config_pathbuf() -> Result<PathBuf> {
let mut pathbuf = get_config_dir_by_platform()?;
pathbuf.push("navi");
pathbuf.push("config.yaml");
if !pathbuf.exists() {
if let Some(path) = compiled_default_path(option_env!("NAVI_CONFIG")) {
pathbuf = path;
}
}
Ok(pathbuf)
}
pub fn cheat_paths(path: Option<String>) -> Result<String> {
if let Some(p) = path {
Ok(p)
} else {
Ok(default_cheat_pathbuf()?.to_string())
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Here are other functions, unrelated to CLI commands (or at least not directly related)
//
////////////////////////////////////////////////////////////////////////////////////////////////////
/// Returns the data dir computed for each platform.
///
/// We are currently handling two cases: When the platform is `macOS` and when the platform isn't (including `Windows` and `Linux/Unix` platforms)
fn get_data_dir_by_platform() -> Result<PathBuf> {
if cfg!(target_os = "macos") {
let base_dirs = etcetera::base_strategy::Apple::new()?;
Ok(base_dirs.data_dir())
} else {
let base_dirs = etcetera::choose_base_strategy()?;
Ok(base_dirs.data_dir())
}
}
/// Returns the config dir computed for each platform.
///
/// We are currently handling two cases: When the platform is `macOS` and when the platform isn't (including `Windows` and `Linux/Unix` platforms)
fn get_config_dir_by_platform() -> Result<PathBuf> {
if cfg!(target_os = "macos") {
let base_dirs = etcetera::base_strategy::Apple::new()?;
Ok(base_dirs.config_dir())
} else {
let base_dirs = etcetera::choose_base_strategy()?;
Ok(base_dirs.config_dir())
}
}
pub fn tmp_pathbuf() -> Result<PathBuf> {
let mut root = default_cheat_pathbuf()?;
root.push("tmp");
Ok(root)
}
fn interpolate_paths(paths: String) -> String {
let re = Regex::new(r#"\$\{?[a-zA-Z_][a-zA-Z_0-9]*"#).unwrap();
let mut newtext = paths.to_string();
for capture in re.captures_iter(&paths) {
if let Some(c) = capture.get(0) {
let varname = c.as_str().replace(['$', '{', '}'], "");
if let Ok(replacement) = &env_var::get(&varname) {
newtext = newtext
.replace(&format!("${varname}"), replacement)
.replace(&format!("${{{varname}}}"), replacement);
}
}
}
newtext
}
#[derive(Debug)]
pub struct Fetcher {
path: Option<String>,
files: RefCell<Vec<String>>,
}
impl Fetcher {
pub fn new(path: Option<String>) -> Self {
Self {
path,
files: Default::default(),
}
}
}
impl fetcher::Fetcher for Fetcher {
fn fetch(&self, parser: &mut Parser) -> Result<bool> {
let mut found_something = false;
let path = self.path.clone();
let paths = cheat_paths(path);
if paths.is_err() {
return Ok(false);
};
let paths = paths.expect("Unable to get paths");
let interpolated_paths = interpolate_paths(paths);
let folders = paths_from_path_param(&interpolated_paths);
let home_regex = Regex::new(r"^~").unwrap();
let home = etcetera::home_dir().ok();
// parser.filter = self.tag_rules.as_ref().map(|r| gen_lists(r.as_str()));
for folder in folders {
let interpolated_folder = match &home {
Some(h) => home_regex.replace(folder, h.to_string_lossy()).to_string(),
None => folder.to_string(),
};
let folder_pathbuf = PathBuf::from(interpolated_folder);
let cheat_files = all_cheat_files(&folder_pathbuf);
debug!("read cheat files in `{folder_pathbuf:?}`: {cheat_files:#?}");
for file in cheat_files {
self.files.borrow_mut().push(file.clone());
let index = self.files.borrow().len() - 1;
let read_file_result = {
let path = PathBuf::from(&file);
let lines = read_lines(&path)?;
parser.read_lines(lines, &file, Some(index))
};
if read_file_result.is_ok() && !found_something {
found_something = true
}
}
}
debug!("FilesystemFetcher = {self:#?}");
Ok(found_something)
}
fn files(&self) -> Vec<String> {
self.files.borrow().clone()
}
}
#[cfg(test)]
mod tests {
use super::*;
/* TODO
use crate::finder::structures::{Opts as FinderOpts, SuggestionType};
use crate::writer;
use std::process::{Command, Stdio};
#[test]
fn test_read_file() {
let path = "tests/cheats/ssh.cheat";
let mut variables = VariableMap::new();
let mut child = Command::new("cat")
.stdin(Stdio::piped())
.stdout(Stdio::null())
.spawn()
.unwrap();
let child_stdin = child.stdin.as_mut().unwrap();
let mut visited_lines: HashSet<u64> = HashSet::new();
let mut writer: Box<dyn Writer> = Box::new(writer::terminal::Writer::new());
read_file(
path,
0,
&mut variables,
&mut visited_lines,
&mut *writer,
child_stdin,
)
.unwrap();
let expected_suggestion = (
r#" echo -e "$(whoami)\nroot" "#.to_string(),
Some(FinderOpts {
header_lines: 0,
column: None,
delimiter: None,
suggestion_type: SuggestionType::SingleSelection,
..Default::default()
}),
);
let actual_suggestion = variables.get_suggestion("ssh", "user");
assert_eq!(Some(&expected_suggestion), actual_suggestion);
}
*/
#[test]
fn splitting_of_dirs_param_may_not_contain_empty_items() {
// 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.
let given_path_config = "SOME_PATH:ANOTHER_PATH:";
let found_paths = paths_from_path_param(given_path_config);
let mut expected_paths = vec!["SOME_PATH", "ANOTHER_PATH"].into_iter();
for found in found_paths {
let expected = expected_paths.next().unwrap();
assert_eq!(found, expected)
}
}
#[test]
fn test_default_config_pathbuf() {
let base_dirs = etcetera::choose_base_strategy().expect("could not determine base directories");
let expected = {
let mut e = base_dirs.config_dir();
e.push("navi");
e.push("config.yaml");
e.to_string_lossy().to_string()
};
let config = default_config_pathbuf().expect("could not find default config path");
assert_eq!(expected, config.to_string_lossy().to_string())
}
#[test]
fn test_default_cheat_pathbuf() {
let base_dirs = etcetera::choose_base_strategy().expect("could not determine base directories");
let expected = {
let mut e = base_dirs.data_dir();
e.push("navi");
e.push("cheats");
e.to_string_lossy().to_string()
};
let cheats = default_cheat_pathbuf().expect("could not find default config path");
assert_eq!(expected, cheats.to_string_lossy().to_string())
}
#[test]
#[cfg(target_family = "windows")]
fn multiple_paths() {
let p = r#"C:\Users\Administrator\AppData\Roaming\navi\config.yaml"#;
let paths = &[p; 2].join(JOIN_SEPARATOR);
assert_eq!(paths_from_path_param(paths).collect::<Vec<_>>(), [p; 2]);
}
}
================================================
FILE: src/finder/mod.rs
================================================
use crate::deser;
use crate::prelude::*;
use std::io::Write;
use std::process::{self, Output};
use std::process::{Command, Stdio};
pub mod structures;
use clap::ValueEnum;
pub use post::process;
use structures::Opts;
use structures::SuggestionType;
const MIN_FZF_VERSION_MAJOR: u32 = 0;
const MIN_FZF_VERSION_MINOR: u32 = 23;
const MIN_FZF_VERSION_PATCH: u32 = 1;
mod post;
#[derive(Debug, Clone, Copy, Deserialize, ValueEnum)]
pub enum FinderChoice {
Fzf,
Skim,
}
impl FromStr for FinderChoice {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"fzf" => Ok(FinderChoice::Fzf),
"skim" => Ok(FinderChoice::Skim),
_ => Err("no match"),
}
}
}
fn parse(out: Output, opts: Opts) -> Result<String> {
let text = match out.status.code() {
Some(0) | Some(1) | Some(2) => {
String::from_utf8(out.stdout).context("Invalid utf8 received from finder")?
}
Some(130) => process::exit(130),
_ => {
let err = String::from_utf8(out.stderr)
.unwrap_or_else(|_| "<stderr contains invalid UTF-8>".to_owned());
panic!("External command failed:\n {err}")
}
};
let output = post::parse_output_single(text, opts.suggestion_type)?;
post::process(output, opts.column, opts.delimiter.as_deref(), opts.map)
}
impl FinderChoice {
fn check_fzf_version() -> Option<(u32, u32, u32)> {
let output = Command::new("fzf").arg("--version").output().ok()?.stdout;
let version_string = String::from_utf8(output).ok()?;
let version_parts: Vec<_> = version_string.split('.').collect();
if version_parts.len() == 3 {
let major = version_parts[0].parse().ok()?;
let minor = version_parts[1].parse().ok()?;
let patch = version_parts[2].split_whitespace().next()?.parse().ok()?;
Some((major, minor, patch))
} else {
None
}
}
pub fn call<F, R>(&self, finder_opts: Opts, stdin_fn: F) -> Result<(String, R)>
where
F: Fn(&mut dyn Write) -> Result<R>,
{
let finder_str = match self {
Self::Fzf => "fzf",
Self::Skim => "sk",
};
if let Self::Fzf = self {
if let Some((major, minor, patch)) = Self::check_fzf_version() {
if major == MIN_FZF_VERSION_MAJOR
&& minor < MIN_FZF_VERSION_MINOR
&& patch < MIN_FZF_VERSION_PATCH
{
eprintln!(
"Warning: Fzf version {major}.{minor} does not support the preview window layout used by navi.",
);
eprintln!(
"Consider updating Fzf to a version >= {MIN_FZF_VERSION_MAJOR}.{MIN_FZF_VERSION_MINOR}.{MIN_FZF_VERSION_PATCH} or use a compatible layout.",
);
process::exit(1);
}
}
}
let mut command = Command::new(finder_str);
let opts = finder_opts.clone();
let preview_height = match self {
FinderChoice::Skim => 3,
_ => 2,
};
let bindings = if opts.suggestion_type == SuggestionType::MultipleSelections {
",ctrl-r:toggle-all"
} else {
""
};
command.args([
"--preview",
"",
"--preview-window",
format!("up:{preview_height}:nohidden").as_str(),
"--delimiter",
deser::terminal::DELIMITER.to_string().as_str(),
"--ansi",
"--bind",
format!("ctrl-j:down,ctrl-k:up{bindings}").as_str(),
"--exact",
]);
if !opts.show_all_columns {
command.args(["--with-nth", "1,2,3"]);
}
if !opts.prevent_select1 {
if let Self::Fzf = self {
command.arg("--select-1");
}
}
match opts.suggestion_type {
SuggestionType::MultipleSelections => {
command.arg("--multi");
}
SuggestionType::Disabled => {
if let Self::Fzf = self {
command.args(["--print-query", "--no-select-1"]);
};
}
SuggestionType::SnippetSelection => {
command.args(["--expect", "ctrl-y,ctrl-o,enter"]);
}
SuggestionType::SingleRecommendation => {
command.args(["--print-query", "--expect", "tab,enter"]);
}
_ => {}
}
if let Some(p) = opts.preview {
command.args(["--preview", &p]);
}
if let Some(q) = opts.query {
command.args(["--query", &q]);
}
if let Some(f) = opts.filter {
command.args(["--filter", &f]);
}
if let Some(d) = opts.delimiter {
command.args(["--delimiter", &d]);
}
if let Some(h) = opts.header {
command.args(["--header", &h]);
}
if let Some(p) = opts.prompt {
command.args(["--prompt", &p]);
}
if let Some(pw) = opts.preview_window {
command.args(["--preview-window", &pw]);
}
if opts.header_lines > 0 {
command.args(["--header-lines", format!("{}", opts.header_lines).as_str()]);
}
if let Some(o) = opts.overrides {
shellwords::split(&o)?
.into_iter()
.filter(|s| !s.is_empty())
.for_each(|s| {
command.arg(s);
});
}
command
.env("SHELL", CONFIG.finder_shell())
.stdin(Stdio::piped())
.stdout(Stdio::piped());
debug!(cmd = ?command);
let child = command.spawn();
let mut child = match child {
Ok(x) => x,
Err(_) => {
let repo = match self {
Self::Fzf => "https://github.com/junegunn/fzf",
Self::Skim => "https://github.com/lotabout/skim",
};
eprintln!(
"navi was unable to call {cmd}.
Please make sure it's correctly installed.
Refer to {repo} for more info.",
cmd = &finder_str,
repo = repo
);
process::exit(33)
}
};
let stdin = child
.stdin
.as_mut()
.ok_or_else(|| anyhow!("Unable to acquire stdin of finder"))?;
let mut writer: Box<&mut dyn Write> = Box::new(stdin);
let return_value = stdin_fn(&mut writer).context("Failed to pass data to finder")?;
let out = child.wait_with_output().context("Failed to wait for finder")?;
let output = parse(out, finder_opts).context("Unable to get output")?;
Ok((output, return_value))
}
}
================================================
FILE: src/finder/post.rs
================================================
use crate::common::shell;
use crate::finder::structures::SuggestionType;
use crate::prelude::*;
use shell::EOF;
use std::process::Stdio;
fn apply_map(text: String, map_fn: Option<String>) -> Result<String> {
if let Some(m) = map_fn {
let cmd = if CONFIG.shell().contains("fish") {
format!(r#"printf "%s" "{text}" | {m}"#)
} else {
format!(
r#"_navi_input() {{
cat <<'{EOF}'
{text}
{EOF}
}}
_navi_map_fn() {{
{m}
}}
_navi_nonewline() {{
printf "%s" "$(cat)"
}}
_navi_input | _navi_map_fn | _navi_nonewline"#
)
};
let output = shell::out()
.arg(cmd.as_str())
.stderr(Stdio::inherit())
.output()
.context("Failed to execute map function")?;
String::from_utf8(output.stdout).context("Invalid utf8 output for map function")
} else {
Ok(text)
}
}
fn get_column(text: String, column: Option<u8>, delimiter: Option<&str>) -> String {
if let Some(c) = column {
let mut result = String::from("");
let re = regex::Regex::new(delimiter.unwrap_or(r"\s\s+")).expect("Invalid regex");
for line in text.split('\n') {
if (line).is_empty() {
continue;
}
let mut parts = re.split(line).skip((c - 1) as usize);
if !result.is_empty() {
result.push('\n');
}
result.push_str(parts.next().unwrap_or(""));
}
result
} else {
text
}
}
pub fn process(
text: String,
column: Option<u8>,
delimiter: Option<&str>,
map_fn: Option<String>,
) -> Result<String> {
apply_map(get_column(text, column, delimiter), map_fn)
}
pub(super) fn parse_output_single(mut text: String, suggestion_type: SuggestionType) -> Result<String> {
Ok(match suggestion_type {
SuggestionType::SingleSelection => text
.lines()
.next()
.context("No sufficient data for single selection")?
.to_string(),
SuggestionType::MultipleSelections | SuggestionType::Disabled | SuggestionType::SnippetSelection => {
let len = text.len();
if len > 1 {
text.truncate(len - 1);
}
text
}
SuggestionType::SingleRecommendation => {
let lines: Vec<&str> = text.lines().collect();
match (lines.first(), lines.get(1), lines.get(2)) {
(Some(one), Some(termination), Some(two))
if *termination == "enter" || termination.is_empty() =>
{
if two.is_empty() {
(*one).to_string()
} else {
(*two).to_string()
}
}
(Some(one), Some(termination), None) if *termination == "enter" || termination.is_empty() => {
(*one).to_string()
}
(Some(one), Some(termination), _) if *termination == "tab" => (*one).to_string(),
_ => "".to_string(),
}
}
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_output1() {
let text = "palo\n".to_string();
let output = parse_output_single(text, SuggestionType::SingleSelection).unwrap();
assert_eq!(output, "palo");
}
#[test]
fn test_parse_output2() {
let text = "\nenter\npalo".to_string();
let output = parse_output_single(text, SuggestionType::SingleRecommendation).unwrap();
assert_eq!(output, "palo");
}
#[test]
fn test_parse_recommendation_output_1() {
let text = "\nenter\npalo".to_string();
let output = parse_output_single(text, SuggestionType::SingleRecommendation).unwrap();
assert_eq!(output, "palo");
}
#[test]
fn test_parse_recommendation_output_2() {
let text = "p\nenter\npalo".to_string();
let output = parse_output_single(text, SuggestionType::SingleRecommendation).unwrap();
assert_eq!(output, "palo");
}
#[test]
fn test_parse_recommendation_output_3() {
let text = "peter\nenter\n".to_string();
let output = parse_output_single(text, SuggestionType::SingleRecommendation).unwrap();
assert_eq!(output, "peter");
}
#[test]
fn test_parse_output3() {
let text = "p\ntab\npalo".to_string();
let output = parse_output_single(text, SuggestionType::SingleRecommendation).unwrap();
assert_eq!(output, "p");
}
#[test]
fn test_parse_snippet_request() {
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();
let output = parse_output_single(text, SuggestionType::SnippetSelection).unwrap();
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> ⠀");
}
}
================================================
FILE: src/finder/structures.rs
================================================
use crate::filesystem;
use crate::prelude::*;
#[derive(Debug, PartialEq, Clone)]
pub struct Opts {
pub query: Option<String>,
pub filter: Option<String>,
pub prompt: Option<String>,
pub preview: Option<String>,
pub preview_window: Option<String>,
pub overrides: Option<String>,
pub header_lines: u8,
pub header: Option<String>,
pub suggestion_type: SuggestionType,
pub delimiter: Option<String>,
pub column: Option<u8>,
pub map: Option<String>,
pub prevent_select1: bool,
pub show_all_columns: bool,
}
impl Default for Opts {
fn default() -> Self {
Self {
query: None,
filter: None,
preview: None,
preview_window: None,
overrides: None,
header_lines: 0,
header: None,
prompt: None,
suggestion_type: SuggestionType::SingleSelection,
column: None,
delimiter: None,
map: None,
prevent_select1: true,
show_all_columns: false,
}
}
}
impl Opts {
pub fn snippet_default() -> Self {
Self {
suggestion_type: SuggestionType::SnippetSelection,
overrides: CONFIG.fzf_overrides(),
preview: Some(format!("{} preview {{}}", filesystem::exe_string())),
prevent_select1: !CONFIG.best_match(),
query: if CONFIG.best_match() {
None
} else {
CONFIG.get_query()
},
filter: if CONFIG.best_match() {
CONFIG.get_query()
} else {
None
},
..Default::default()
}
}
pub fn var_default() -> Self {
Self {
overrides: CONFIG.fzf_overrides_var(),
suggestion_type: SuggestionType::SingleRecommendation,
prevent_select1: false,
delimiter: CONFIG.delimiter_var(),
..Default::default()
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum SuggestionType {
/// finder will not print any suggestions
Disabled,
/// finder will only select one of the suggestions
SingleSelection,
/// finder will select multiple suggestions
MultipleSelections,
/// finder will select one of the suggestions or use the query
SingleRecommendation,
/// initial snippet selection
SnippetSelection,
}
================================================
FILE: src/lib.rs
================================================
#[macro_use]
extern crate lazy_static;
mod clients;
mod commands;
mod common;
mod config;
mod deser;
mod env_var;
mod filesystem;
mod finder;
mod parser;
pub mod prelude;
mod structures;
mod welcome;
mod libs {
pub mod dns_common;
}
pub use {commands::handle, filesystem::default_config_pathbuf};
================================================
FILE: src/libs/dns_common/component.rs
================================================
use crate::prelude::*;
pub trait Component: Any + AsAny + Send + Sync {}
pub trait AsAny: Any {
fn as_any(&self) -> &dyn Any;
fn as_mut_any(&mut self) -> &mut dyn Any;
}
================================================
FILE: src/libs/dns_common/mod.rs
================================================
pub mod component;
mod tracing;
================================================
FILE: src/libs/dns_common/tracing.rs
================================================
use crate::prelude::*;
#[derive(Deserialize, Serialize, Debug, Clone)]
#[serde(deny_unknown_fields)]
pub struct TracingConfig {
pub time: bool,
pub level: String,
}
================================================
FILE: src/parser.rs
================================================
use crate::common::fs;
use crate::deser;
use crate::finder::structures::{Opts as FinderOpts, SuggestionType};
use crate::prelude::*;
use crate::structures::cheat::VariableMap;
use crate::structures::item::Item;
use std::io::Write;
lazy_static! {
pub static ref VAR_LINE_REGEX: Regex = Regex::new(r"^\$\s*([^:]+):(.*)").expect("Invalid regex");
}
fn parse_opts(text: &str) -> Result<FinderOpts> {
let mut multi = false;
let mut prevent_extra = false;
let mut opts = FinderOpts::var_default();
let parts = shellwords::split(text).map_err(|_| anyhow!("Given options are missing a closing quote"))?;
parts
.into_iter()
.filter
gitextract_8cygam37/
├── .github/
│ ├── CODEOWNERS
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ └── feature_request.md
│ ├── config.yml
│ └── workflows/
│ ├── cd.yml
│ └── ci.yml
├── .gitignore
├── Cargo.toml
├── LICENSE
├── Makefile
├── README.md
├── docs/
│ ├── README.md
│ ├── cheatsheet/
│ │ ├── README.md
│ │ ├── getting-started/
│ │ │ └── README.md
│ │ ├── repositories/
│ │ │ └── README.md
│ │ └── syntax/
│ │ └── README.md
│ ├── configuration/
│ │ └── README.md
│ ├── contributions/
│ │ ├── README.md
│ │ ├── bugs/
│ │ │ └── README.md
│ │ ├── code/
│ │ │ └── README.md
│ │ └── documentation/
│ │ └── README.md
│ ├── deprecated/
│ │ └── Alfred/
│ │ └── README.md
│ ├── examples/
│ │ ├── cheatsheet/
│ │ │ ├── example.cheat
│ │ │ └── navi.cheat
│ │ └── configuration/
│ │ └── config-example.yaml
│ ├── installation/
│ │ └── README.md
│ ├── usage/
│ │ ├── README.md
│ │ ├── commands/
│ │ │ ├── info/
│ │ │ │ └── README.md
│ │ │ └── repo/
│ │ │ └── README.md
│ │ ├── fzf-overrides/
│ │ │ └── README.md
│ │ └── shell-scripting/
│ │ └── README.md
│ └── widgets/
│ ├── README.md
│ └── howto/
│ ├── TMUX.md
│ └── VIM.md
├── rust-toolchain.toml
├── rustfmt.toml
├── scripts/
│ ├── docker
│ ├── dot
│ ├── install
│ ├── make
│ ├── release
│ └── test
├── shell/
│ ├── navi.plugin.bash
│ ├── navi.plugin.elv
│ ├── navi.plugin.fish
│ ├── navi.plugin.nu
│ ├── navi.plugin.ps1
│ └── navi.plugin.zsh
├── src/
│ ├── bin/
│ │ └── main.rs
│ ├── clients/
│ │ ├── cheatsh.rs
│ │ ├── mod.rs
│ │ └── tldr.rs
│ ├── commands/
│ │ ├── core/
│ │ │ ├── actor.rs
│ │ │ └── mod.rs
│ │ ├── func/
│ │ │ ├── map.rs
│ │ │ ├── mod.rs
│ │ │ └── widget.rs
│ │ ├── info.rs
│ │ ├── mod.rs
│ │ ├── preview/
│ │ │ ├── mod.rs
│ │ │ ├── var.rs
│ │ │ └── var_stdin.rs
│ │ ├── repo/
│ │ │ ├── add.rs
│ │ │ ├── browse.rs
│ │ │ └── mod.rs
│ │ ├── shell.rs
│ │ └── temp.rs
│ ├── common/
│ │ ├── clipboard.rs
│ │ ├── deps.rs
│ │ ├── fs.rs
│ │ ├── git.rs
│ │ ├── hash.rs
│ │ ├── mod.rs
│ │ ├── shell.rs
│ │ ├── terminal.rs
│ │ └── url.rs
│ ├── config/
│ │ ├── cli.rs
│ │ ├── env.rs
│ │ ├── mod.rs
│ │ └── yaml.rs
│ ├── deser/
│ │ ├── mod.rs
│ │ ├── raycast.rs
│ │ └── terminal.rs
│ ├── env_var.rs
│ ├── filesystem.rs
│ ├── finder/
│ │ ├── mod.rs
│ │ ├── post.rs
│ │ └── structures.rs
│ ├── lib.rs
│ ├── libs/
│ │ └── dns_common/
│ │ ├── component.rs
│ │ ├── mod.rs
│ │ └── tracing.rs
│ ├── parser.rs
│ ├── prelude.rs
│ ├── structures/
│ │ ├── cheat.rs
│ │ ├── fetcher.rs
│ │ ├── item.rs
│ │ └── mod.rs
│ └── welcome.rs
└── tests/
├── cheats/
│ ├── more_cases.cheat
│ └── ssh.cheat
├── config.yaml
├── core.bash
├── helpers.sh
├── no_prompt_cheats/
│ ├── cases.cheat
│ └── one.cheat
├── run
└── tests.rs
SYMBOL INDEX (260 symbols across 47 files)
FILE: src/bin/main.rs
type FileAnIssue (line 11) | pub struct FileAnIssue {
method new (line 17) | pub fn new<SourceError>(source: SourceError) -> Self
function main (line 27) | fn main() -> anyhow::Result<()> {
function init_logger (line 38) | fn init_logger() -> anyhow::Result<()> {
FILE: src/clients/cheatsh.rs
function map_line (line 4) | fn map_line(line: &str) -> String {
function as_lines (line 8) | fn as_lines(query: &str, markdown: &str) -> Vec<String> {
function call (line 18) | pub fn call(query: &str) -> Result<Vec<String>> {
FILE: src/clients/tldr.rs
function convert_tldr_vars (line 14) | fn convert_tldr_vars(line: &str) -> String {
function convert_tldr (line 32) | fn convert_tldr(line: &str) -> String {
function markdown_lines (line 45) | fn markdown_lines(query: &str, markdown: &str) -> Vec<String> {
function call (line 55) | pub fn call(query: &str) -> Result<Vec<String>> {
FILE: src/commands/core/actor.rs
function prompt_finder (line 15) | fn prompt_finder(
function unique_result_count (line 151) | fn unique_result_count(results: &[&str]) -> usize {
function replace_variables_from_snippet (line 158) | fn replace_variables_from_snippet(snippet: &str, tags: &str, variables: ...
function with_absolute_path (line 196) | pub fn with_absolute_path(snippet: String) -> String {
function act (line 203) | pub fn act(
FILE: src/commands/core/mod.rs
function init (line 13) | pub fn init(fetcher: Box<dyn Fetcher>) -> Result<()> {
function get_fetcher (line 48) | pub fn get_fetcher() -> Result<Box<dyn Fetcher>> {
function main (line 73) | pub fn main() -> Result<()> {
FILE: src/commands/func/map.rs
function expand (line 4) | pub fn expand() -> Result<()> {
FILE: src/commands/func/mod.rs
type Func (line 12) | pub enum Func {
type Input (line 26) | pub struct Input {
method run (line 35) | fn run(&self) -> Result<()> {
FILE: src/commands/func/widget.rs
function last_command (line 4) | pub fn last_command() -> Result<()> {
FILE: src/commands/info.rs
type Input (line 6) | pub struct Input {
type Info (line 12) | pub enum Info {
method run (line 32) | fn run(&self) -> Result<()> {
FILE: src/commands/mod.rs
function handle (line 12) | pub fn handle() -> Result<()> {
FILE: src/commands/preview/mod.rs
type Input (line 11) | pub struct Input {
function extract_elements (line 16) | fn extract_elements(argstr: &str) -> Result<(&str, &str, &str)> {
method run (line 25) | fn run(&self) -> Result<()> {
FILE: src/commands/preview/var.rs
type Input (line 12) | pub struct Input {
method run (line 22) | fn run(&self) -> Result<()> {
FILE: src/commands/preview/var_stdin.rs
type Input (line 9) | pub struct Input {}
method run (line 12) | fn run(&self) -> Result<()> {
FILE: src/commands/repo/add.rs
function ask_if_should_import_all (line 9) | fn ask_if_should_import_all(finder: &FinderChoice) -> Result<bool> {
function main (line 28) | pub fn main(uri: String) -> Result<()> {
FILE: src/commands/repo/browse.rs
function main (line 8) | pub fn main() -> Result<String> {
FILE: src/commands/repo/mod.rs
type RepoCommand (line 9) | pub enum RepoCommand {
type Input (line 20) | pub struct Input {
method run (line 26) | fn run(&self) -> Result<()> {
FILE: src/commands/shell.rs
method fmt (line 10) | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
type Input (line 25) | pub struct Input {
method run (line 31) | fn run(&self) -> Result<()> {
FILE: src/commands/temp.rs
function main (line 8) | pub fn main() -> Result<()> {
function _main0 (line 51) | pub fn _main0() -> Result<()> {
FILE: src/common/clipboard.rs
function copy (line 4) | pub fn copy(text: String) -> Result<()> {
FILE: src/common/deps.rs
type HasDeps (line 3) | pub trait HasDeps {
method deps (line 4) | fn deps(&self) -> HashSet<TypeId> {
FILE: src/common/fs.rs
type ToStringExt (line 8) | pub trait ToStringExt {
method to_string (line 9) | fn to_string(&self) -> String;
method to_string (line 13) | fn to_string(&self) -> String {
method to_string (line 19) | fn to_string(&self) -> String {
type InvalidPath (line 26) | pub struct InvalidPath(pub PathBuf);
type UnreadableDir (line 30) | pub struct UnreadableDir {
function open (line 36) | pub fn open(filename: &Path) -> Result<File> {
function read_lines (line 43) | pub fn read_lines(filename: &Path) -> Result<impl Iterator<Item = Result...
function pathbuf_to_string (line 50) | pub fn pathbuf_to_string(pathbuf: &Path) -> Result<String> {
function follow_symlink (line 58) | fn follow_symlink(pathbuf: PathBuf) -> Result<PathBuf> {
function exe_pathbuf (line 79) | fn exe_pathbuf() -> Result<PathBuf> {
function exe_abs_string (line 89) | fn exe_abs_string() -> Result<String> {
function exe_string (line 93) | pub fn exe_string() -> String {
function create_dir (line 97) | pub fn create_dir(path: &Path) -> Result<()> {
function remove_dir (line 106) | pub fn remove_dir(path: &Path) -> Result<()> {
FILE: src/common/git.rs
function shallow_clone (line 5) | pub fn shallow_clone(uri: &str, target: &str) -> Result<()> {
function meta (line 15) | pub fn meta(uri: &str) -> (String, String, String) {
function test_meta_github_https (line 35) | fn test_meta_github_https() {
function test_meta_github_ssh (line 43) | fn test_meta_github_ssh() {
function test_meta_gitlab_https (line 51) | fn test_meta_gitlab_https() {
FILE: src/common/hash.rs
constant MAGIC_INIT (line 3) | const MAGIC_INIT: u64 = 0x811C_9DC5;
function fnv (line 5) | pub fn fnv<T: Hash>(x: &T) -> u64 {
type FnvHasher (line 11) | struct FnvHasher(u64);
method new (line 14) | fn new() -> Self {
method finish (line 20) | fn finish(&self) -> u64 {
method write (line 24) | fn write(&mut self, bytes: &[u8]) {
FILE: src/common/shell.rs
constant EOF (line 6) | pub const EOF: &str = "NAVIEOF";
type Shell (line 9) | pub enum Shell {
type ShellSpawnError (line 20) | pub struct ShellSpawnError {
method new (line 27) | pub fn new<SourceError>(command: impl Into<String>, source: SourceErro...
function out (line 38) | pub fn out() -> Command {
FILE: src/common/terminal.rs
constant FALLBACK_WIDTH (line 7) | const FALLBACK_WIDTH: u16 = 80;
function width_with_shell_out (line 9) | fn width_with_shell_out() -> Result<u16> {
function width (line 40) | pub fn width() -> u16 {
function parse_ansi (line 48) | pub fn parse_ansi(ansi: &str) -> Option<style::Color> {
type Color (line 53) | pub struct Color(#[allow(unused)] pub style::Color);
type Err (line 56) | type Err = &'static str;
method from_str (line 58) | fn from_str(ansi: &str) -> Result<Self, Self::Err> {
FILE: src/common/url.rs
function open (line 6) | pub fn open(args: Vec<String>) -> Result<()> {
FILE: src/config/cli.rs
type ClapConfig (line 41) | pub(super) struct ClapConfig {
method new (line 92) | pub fn new() -> Self {
type Command (line 99) | pub enum Command {
type Source (line 121) | pub enum Source {
type Action (line 128) | pub enum Action {
FILE: src/config/env.rs
type EnvConfig (line 6) | pub struct EnvConfig {
method new (line 16) | pub fn new() -> Self {
FILE: src/config/mod.rs
type Config (line 17) | pub struct Config {
method new (line 24) | pub fn new() -> Self {
method best_match (line 36) | pub fn best_match(&self) -> bool {
method prevent_interpolation (line 40) | pub fn prevent_interpolation(&self) -> bool {
method cmd (line 44) | pub fn cmd(&self) -> Option<&Command> {
method source (line 48) | pub fn source(&self) -> Source {
method path (line 64) | pub fn path(&self) -> Option<String> {
method finder (line 106) | pub fn finder(&self) -> FinderChoice {
method fzf_overrides (line 113) | pub fn fzf_overrides(&self) -> Option<String> {
method fzf_overrides_var (line 121) | pub fn fzf_overrides_var(&self) -> Option<String> {
method delimiter_var (line 129) | pub fn delimiter_var(&self) -> Option<String> {
method tealdeer (line 133) | pub fn tealdeer(&self) -> bool {
method shell (line 137) | pub fn shell(&self) -> String {
method finder_shell (line 141) | pub fn finder_shell(&self) -> String {
method tag_rules (line 149) | pub fn tag_rules(&self) -> Option<String> {
method tag_color (line 156) | pub fn tag_color(&self) -> Color {
method comment_color (line 160) | pub fn comment_color(&self) -> Color {
method snippet_color (line 164) | pub fn snippet_color(&self) -> Color {
method tag_width_percentage (line 168) | pub fn tag_width_percentage(&self) -> u16 {
method comment_width_percentage (line 172) | pub fn comment_width_percentage(&self) -> u16 {
method snippet_width_percentage (line 176) | pub fn snippet_width_percentage(&self) -> u16 {
method tag_min_width (line 180) | pub fn tag_min_width(&self) -> u16 {
method comment_min_width (line 184) | pub fn comment_min_width(&self) -> u16 {
method snippet_min_width (line 188) | pub fn snippet_min_width(&self) -> u16 {
method print (line 193) | fn print(&self) -> bool {
method print (line 198) | fn print(&self) -> bool {
method action (line 202) | pub fn action(&self) -> Action {
method get_query (line 210) | pub fn get_query(&self) -> Option<String> {
method default (line 228) | fn default() -> Self {
FILE: src/config/yaml.rs
type Color (line 10) | pub struct Color(#[serde(deserialize_with = "color_deserialize")] Termin...
method get (line 13) | pub fn get(&self) -> TerminalColor {
function color_deserialize (line 18) | fn color_deserialize<'de, D>(deserializer: D) -> Result<TerminalColor, D...
type ColorWidth (line 29) | pub struct ColorWidth {
type Style (line 37) | pub struct Style {
type Finder (line 45) | pub struct Finder {
function finder_deserialize (line 53) | fn finder_deserialize<'de, D>(deserializer: D) -> Result<FinderChoice, D...
type Cheats (line 64) | pub struct Cheats {
type Search (line 71) | pub struct Search {
type Shell (line 77) | pub struct Shell {
type Client (line 85) | pub struct Client {
type YamlConfig (line 91) | pub struct YamlConfig {
method from_str (line 102) | fn from_str(text: &str) -> Result<Self> {
method from_path (line 106) | fn from_path(path: &Path) -> Result<Self> {
method get (line 112) | pub fn get(env: &EnvConfig) -> Result<YamlConfig> {
method default (line 147) | fn default() -> Self {
method default (line 157) | fn default() -> Self {
method default (line 175) | fn default() -> Self {
method default (line 186) | fn default() -> Self {
method default (line 195) | fn default() -> Self {
FILE: src/deser/mod.rs
constant NEWLINE_ESCAPE_CHAR (line 7) | const NEWLINE_ESCAPE_CHAR: char = '\x15';
constant LINE_SEPARATOR (line 8) | pub const LINE_SEPARATOR: &str = " \x15 ";
function with_new_lines (line 15) | pub fn with_new_lines(txt: String) -> String {
function fix_newlines (line 19) | pub fn fix_newlines(txt: &str) -> String {
function limit_str (line 29) | fn limit_str(text: &str, length: usize) -> String {
FILE: src/deser/raycast.rs
constant FIELD_SEP_ESCAPE_CHAR (line 4) | const FIELD_SEP_ESCAPE_CHAR: char = '\x16';
function write (line 6) | pub fn write(item: &Item) -> String {
function read (line 18) | pub fn read(line: &str) -> Result<Item> {
FILE: src/deser/terminal.rs
function get_widths (line 7) | pub fn get_widths() -> (usize, usize, usize) {
constant DELIMITER (line 28) | pub const DELIMITER: &str = r" ⠀";
function write (line 34) | pub fn write(item: &Item) -> String {
function read (line 49) | pub fn read(raw_snippet: &str, is_single: bool) -> Result<(&str, Item)> {
FILE: src/env_var.rs
constant PREVIEW_INITIAL_SNIPPET (line 7) | pub const PREVIEW_INITIAL_SNIPPET: &str = "NAVI_PREVIEW_INITIAL_SNIPPET";
constant PREVIEW_TAGS (line 8) | pub const PREVIEW_TAGS: &str = "NAVI_PREVIEW_TAGS";
constant PREVIEW_COMMENT (line 9) | pub const PREVIEW_COMMENT: &str = "NAVI_PREVIEW_COMMENT";
constant PREVIEW_COLUMN (line 10) | pub const PREVIEW_COLUMN: &str = "NAVI_PREVIEW_COLUMN";
constant PREVIEW_DELIMITER (line 11) | pub const PREVIEW_DELIMITER: &str = "NAVI_PREVIEW_DELIMITER";
constant PREVIEW_MAP (line 12) | pub const PREVIEW_MAP: &str = "NAVI_PREVIEW_MAP";
constant PATH (line 14) | pub const PATH: &str = "NAVI_PATH";
constant FZF_OVERRIDES (line 15) | pub const FZF_OVERRIDES: &str = "NAVI_FZF_OVERRIDES";
constant FZF_OVERRIDES_VAR (line 16) | pub const FZF_OVERRIDES_VAR: &str = "NAVI_FZF_OVERRIDES_VAR";
constant FINDER (line 17) | pub const FINDER: &str = "NAVI_FINDER";
constant CONFIG (line 19) | pub const CONFIG: &str = "NAVI_CONFIG";
constant CONFIG_YAML (line 20) | pub const CONFIG_YAML: &str = "NAVI_CONFIG_YAML";
function parse (line 22) | pub fn parse<T: FromStr>(varname: &str) -> Option<T> {
function must_get (line 30) | pub fn must_get(name: &str) -> String {
function escape (line 38) | pub fn escape(name: &str) -> String {
FILE: src/filesystem.rs
constant JOIN_SEPARATOR (line 18) | pub const JOIN_SEPARATOR: &str = ";";
constant JOIN_SEPARATOR (line 20) | pub const JOIN_SEPARATOR: &str = ":";
function all_cheat_files (line 22) | pub fn all_cheat_files(path: &Path) -> Vec<String> {
function paths_from_path_param (line 32) | fn paths_from_path_param(env_var: &str) -> impl Iterator<Item = &str> {
function compiled_default_path (line 36) | fn compiled_default_path(path: Option<&str>) -> Option<PathBuf> {
function default_cheat_pathbuf (line 55) | pub fn default_cheat_pathbuf() -> Result<PathBuf> {
function default_config_pathbuf (line 69) | pub fn default_config_pathbuf() -> Result<PathBuf> {
function cheat_paths (line 83) | pub fn cheat_paths(path: Option<String>) -> Result<String> {
function get_data_dir_by_platform (line 100) | fn get_data_dir_by_platform() -> Result<PathBuf> {
function get_config_dir_by_platform (line 115) | fn get_config_dir_by_platform() -> Result<PathBuf> {
function tmp_pathbuf (line 127) | pub fn tmp_pathbuf() -> Result<PathBuf> {
function interpolate_paths (line 133) | fn interpolate_paths(paths: String) -> String {
type Fetcher (line 150) | pub struct Fetcher {
method new (line 156) | pub fn new(path: Option<String>) -> Self {
method fetch (line 165) | fn fetch(&self, parser: &mut Parser) -> Result<bool> {
method files (line 211) | fn files(&self) -> Vec<String> {
function splitting_of_dirs_param_may_not_contain_empty_items (line 263) | fn splitting_of_dirs_param_may_not_contain_empty_items() {
function test_default_config_pathbuf (line 278) | fn test_default_config_pathbuf() {
function test_default_cheat_pathbuf (line 294) | fn test_default_cheat_pathbuf() {
function multiple_paths (line 311) | fn multiple_paths() {
FILE: src/finder/mod.rs
constant MIN_FZF_VERSION_MAJOR (line 12) | const MIN_FZF_VERSION_MAJOR: u32 = 0;
constant MIN_FZF_VERSION_MINOR (line 13) | const MIN_FZF_VERSION_MINOR: u32 = 23;
constant MIN_FZF_VERSION_PATCH (line 14) | const MIN_FZF_VERSION_PATCH: u32 = 1;
type FinderChoice (line 19) | pub enum FinderChoice {
method check_fzf_version (line 54) | fn check_fzf_version() -> Option<(u32, u32, u32)> {
method call (line 68) | pub fn call<F, R>(&self, finder_opts: Opts, stdin_fn: F) -> Result<(St...
type Err (line 25) | type Err = &'static str;
method from_str (line 27) | fn from_str(s: &str) -> Result<Self, Self::Err> {
function parse (line 36) | fn parse(out: Output, opts: Opts) -> Result<String> {
FILE: src/finder/post.rs
function apply_map (line 7) | fn apply_map(text: String, map_fn: Option<String>) -> Result<String> {
function get_column (line 43) | fn get_column(text: String, column: Option<u8>, delimiter: Option<&str>)...
function process (line 63) | pub fn process(
function parse_output_single (line 72) | pub(super) fn parse_output_single(mut text: String, suggestion_type: Sug...
function test_parse_output1 (line 114) | fn test_parse_output1() {
function test_parse_output2 (line 121) | fn test_parse_output2() {
function test_parse_recommendation_output_1 (line 128) | fn test_parse_recommendation_output_1() {
function test_parse_recommendation_output_2 (line 135) | fn test_parse_recommendation_output_2() {
function test_parse_recommendation_output_3 (line 142) | fn test_parse_recommendation_output_3() {
function test_parse_output3 (line 149) | fn test_parse_output3() {
function test_parse_snippet_request (line 156) | fn test_parse_snippet_request() {
FILE: src/finder/structures.rs
type Opts (line 5) | pub struct Opts {
method snippet_default (line 44) | pub fn snippet_default() -> Self {
method var_default (line 64) | pub fn var_default() -> Self {
method default (line 23) | fn default() -> Self {
type SuggestionType (line 76) | pub enum SuggestionType {
FILE: src/libs/dns_common/component.rs
type Component (line 3) | pub trait Component: Any + AsAny + Send + Sync {}
type AsAny (line 5) | pub trait AsAny: Any {
method as_any (line 6) | fn as_any(&self) -> &dyn Any;
method as_mut_any (line 7) | fn as_mut_any(&mut self) -> &mut dyn Any;
FILE: src/libs/dns_common/tracing.rs
type TracingConfig (line 5) | pub struct TracingConfig {
FILE: src/parser.rs
function parse_opts (line 13) | fn parse_opts(text: &str) -> Result<FinderOpts> {
function parse_variable_line (line 87) | fn parse_variable_line(line: &str) -> Result<(&str, &str, Option<FinderO...
function without_prefix (line 108) | fn without_prefix(line: &str) -> String {
type FilterOpts (line 117) | pub struct FilterOpts {
type Parser (line 123) | pub struct Parser<'a> {
function without_first (line 131) | fn without_first(string: &str) -> String {
function gen_lists (line 140) | fn gen_lists(tag_rules: &str) -> FilterOpts {
function new (line 163) | pub fn new(writer: &'a mut dyn Write, is_terminal: bool) -> Self {
function set_hash (line 184) | pub fn set_hash(&mut self, hash: u64) {
function write_cmd (line 188) | fn write_cmd(&mut self, item: &Item) -> Result<()> {
function read_lines (line 233) | pub fn read_lines(
function test_parse_variable_line (line 339) | fn test_parse_variable_line() {
FILE: src/prelude.rs
type Component (line 21) | pub trait Component: Any + AsAny + Send + Sync {}
type AsAny (line 23) | pub trait AsAny: Any {
method as_any (line 24) | fn as_any(&self) -> &dyn Any;
method as_mut_any (line 25) | fn as_mut_any(&mut self) -> &mut dyn Any;
method as_any (line 32) | fn as_any(&self) -> &dyn Any {
method as_mut_any (line 36) | fn as_mut_any(&mut self) -> &mut dyn Any {
type Runnable (line 41) | pub trait Runnable {
method run (line 42) | fn run(&self) -> Result<()>;
FILE: src/structures/cheat.rs
type Suggestion (line 5) | pub type Suggestion = (String, Option<Opts>);
type VariableMap (line 8) | pub struct VariableMap {
method insert_dependency (line 14) | pub fn insert_dependency(&mut self, tags: &str, tags_dependency: &str) {
method insert_suggestion (line 24) | pub fn insert_suggestion(&mut self, tags: &str, variable: &str, value:...
method get_suggestion (line 36) | pub fn get_suggestion(&self, tags: &str, variable: &str) -> Option<&Su...
FILE: src/structures/fetcher.rs
type Fetcher (line 4) | pub trait Fetcher {
method fetch (line 5) | fn fetch(&self, parser: &mut Parser) -> Result<bool>;
method files (line 7) | fn files(&self) -> Vec<String> {
method fetch (line 23) | fn fetch(&self, parser: &mut Parser) -> Result<bool> {
type StaticFetcher (line 12) | pub struct StaticFetcher {
method new (line 17) | pub fn new(lines: Vec<String>) -> Self {
FILE: src/structures/item.rs
type Item (line 4) | pub struct Item {
method new (line 13) | pub fn new(file_index: Option<usize>) -> Self {
method hash (line 20) | pub fn hash(&self) -> u64 {
FILE: src/welcome.rs
function populate_cheatsheet (line 5) | pub fn populate_cheatsheet(parser: &mut Parser) -> Result<()> {
type Fetcher (line 14) | pub struct Fetcher {}
method new (line 17) | pub fn new() -> Self {
method fetch (line 23) | fn fetch(&self, parser: &mut Parser) -> Result<bool> {
FILE: tests/tests.rs
function it_works (line 4) | fn it_works() {
Condensed preview — 109 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (229K chars).
[
{
"path": ".github/CODEOWNERS",
"chars": 263,
"preview": "# See: https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-reposito"
},
{
"path": ".github/FUNDING.yml",
"chars": 19,
"preview": "github: denisidoro\n"
},
{
"path": ".github/ISSUE_TEMPLATE/bug_report.md",
"chars": 661,
"preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: \"\"\nlabels: bug\nassignees: \"\"\n---\n\n**Describe the b"
},
{
"path": ".github/ISSUE_TEMPLATE/feature_request.md",
"chars": 603,
"preview": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: \"\"\nlabels: new feature\nassignees: \"\"\n---\n\n**Is "
},
{
"path": ".github/config.yml",
"chars": 775,
"preview": "# Configuration for new-issue-welcome - https://github.com/behaviorbot/new-issue-welcome\n\n# Comment to be posted to on f"
},
{
"path": ".github/workflows/cd.yml",
"chars": 2431,
"preview": "name: Publish\n\non:\n push:\n tags:\n - \"*\"\n release:\n\njobs:\n binary:\n name: Publish ${{ matrix.target }}\n "
},
{
"path": ".github/workflows/ci.yml",
"chars": 2649,
"preview": "# Based on https://github.com/actions-rs/meta/blob/master/recipes/quickstart.md\n#\n# While our \"example\" application has "
},
{
"path": ".gitignore",
"chars": 28,
"preview": "/target\n**/*.rs.bk\nnavi.log\n"
},
{
"path": "Cargo.toml",
"chars": 1390,
"preview": "[package]\nname = \"navi\"\nversion = \"2.25.0-beta1\"\nauthors = [\"Denis Isidoro <denis_isidoro@live.com>\", \"Alexis Opolka <al"
},
{
"path": "LICENSE",
"chars": 11357,
"preview": "\n Apache License\n Version 2.0, January 2004\n "
},
{
"path": "Makefile",
"chars": 181,
"preview": "export PATH := /usr/local/opt/bash/bin/:$(PATH)\n\ninstall:\n\tscripts/make install\n\nuninstall:\n\tscripts/make uninstall\n\nfix"
},
{
"path": "README.md",
"chars": 4398,
"preview": "# navi <img src=\"https://raw.githubusercontent.com/denisidoro/navi/master/assets/icon.png\" alt=\"icon\" height=\"28px\"/> [!"
},
{
"path": "docs/README.md",
"chars": 2713,
"preview": "# Navi <img src=\"https://raw.githubusercontent.com/denisidoro/navi/master/assets/icon.png\" alt=\"icon\" height=\"28px\"/> [!"
},
{
"path": "docs/cheatsheet/README.md",
"chars": 2071,
"preview": "# Navi cheatsheets\n\n<!-- TOC -->\n* [Navi cheatsheets](#navi-cheatsheets)\n * [Working with `cheatsheet repositories`](#w"
},
{
"path": "docs/cheatsheet/getting-started/README.md",
"chars": 32,
"preview": "# Cheatsheets - Getting started\n"
},
{
"path": "docs/cheatsheet/repositories/README.md",
"chars": 2312,
"preview": "# Cheatsheet repositories\r\n\r\n<!-- TOC -->\r\n* [Cheatsheet repositories](#cheatsheet-repositories)\r\n * [About](#about)\r\n "
},
{
"path": "docs/cheatsheet/syntax/README.md",
"chars": 8770,
"preview": "# The syntax of a Navi cheatsheet\n\n<!-- TOC -->\n* [The syntax of a Navi cheatsheet](#the-syntax-of-a-navi-cheatsheet)\n "
},
{
"path": "docs/configuration/README.md",
"chars": 7145,
"preview": "# Configuring Navi\n\nNavi allows you to configure it with a YAML configuration.\n\n<!-- TOC -->\n* [Configuring Navi](#confi"
},
{
"path": "docs/contributions/README.md",
"chars": 1810,
"preview": "# 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* ["
},
{
"path": "docs/contributions/bugs/README.md",
"chars": 279,
"preview": "# 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, w"
},
{
"path": "docs/contributions/code/README.md",
"chars": 2372,
"preview": "# Contribute code to Navi\r\n\r\nNavi is written in Rust, the widgets may be written in any language given it can be integra"
},
{
"path": "docs/contributions/documentation/README.md",
"chars": 971,
"preview": "# 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 "
},
{
"path": "docs/deprecated/Alfred/README.md",
"chars": 839,
"preview": "# Alfred \n\n> [!CAUTION]\n> This feature has been deprecated and support has been dropped since 2.16.0.\n>\n> The latest ver"
},
{
"path": "docs/examples/cheatsheet/example.cheat",
"chars": 81,
"preview": "% 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",
"chars": 723,
"preview": "% cheatsheets\n\t \n# Download default cheatsheets\nnavi repo add denisidoro/cheats\n\n# Browse for cheatsheet repos\nnavi repo"
},
{
"path": "docs/examples/configuration/config-example.yaml",
"chars": 1196,
"preview": "# THIS IS EXPERIMENTAL\n# the config file schema may change at any time\n\nstyle:\n tag:\n color: cyan # text color. poss"
},
{
"path": "docs/installation/README.md",
"chars": 3763,
"preview": "# Installation of navi\n\nThis is a reference of all known methods to install navi.\n\n> [!CAUTION]\n> Navi, as of now, has o"
},
{
"path": "docs/usage/README.md",
"chars": 745,
"preview": "# The usage of Navi\n\nNavi can be used in multiple ways\n\n#### Defining the cheatsheets path at runtime\n\nYou can define th"
},
{
"path": "docs/usage/commands/info/README.md",
"chars": 2206,
"preview": "# The info subcommands of navi\n\nNavi exposes information about its default values or examples for you to use.\n\n<!-- TOC "
},
{
"path": "docs/usage/commands/repo/README.md",
"chars": 1690,
"preview": "# The repo subcommands of navi\n\n<!-- TOC -->\n* [The repo subcommands of navi](#the-repo-subcommands-of-navi)\n * [Comman"
},
{
"path": "docs/usage/fzf-overrides/README.md",
"chars": 788,
"preview": "# The FZF Overrides of Navi\n\nNavi allows you to override certain parts of FZF in multiple ways.\n\n<!-- TOC -->\n* [The FZF"
},
{
"path": "docs/usage/shell-scripting/README.md",
"chars": 1807,
"preview": "# Navi and shell scripting\n\nYou can use Navi with shell scripting.\n\n<!-- TOC -->\n* [Navi and shell scripting](#navi-and-"
},
{
"path": "docs/widgets/README.md",
"chars": 1994,
"preview": "# Navi widgets\n\nYou want to launch Navi with a shortcut?\\\nWidgets are here for you!\n\nWidgets are 3rd-party contributions"
},
{
"path": "docs/widgets/howto/TMUX.md",
"chars": 1116,
"preview": "# 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"
},
{
"path": "docs/widgets/howto/VIM.md",
"chars": 583,
"preview": "# Vim widget\n\n<!-- TOC -->\n* [Vim widget](#vim-widget)\n * [Syntax Highlighting](#syntax-highlighting)\n<!-- TOC -->\n\n## "
},
{
"path": "rust-toolchain.toml",
"chars": 68,
"preview": "[toolchain]\nchannel = \"1.81.0\"\ncomponents = [ \"rustfmt\", \"clippy\" ]\n"
},
{
"path": "rustfmt.toml",
"chars": 16,
"preview": "max_width = 110\n"
},
{
"path": "scripts/docker",
"chars": 622,
"preview": "#!/usr/bin/env bash\nset -euo pipefail\n\nexport NAVI_HOME=\"$(cd \"$(dirname \"$0\")/..\" && pwd)\"\n\n_start() {\n cd \"$NAVI_HOM"
},
{
"path": "scripts/dot",
"chars": 852,
"preview": "#!/usr/bin/env bash\nset -euo pipefail\n\nexport NAVI_HOME=\"$(cd \"$(dirname \"$0\")/..\" && pwd)\"\nexport PROJ_HOME=\"$NAVI_HOME"
},
{
"path": "scripts/install",
"chars": 6597,
"preview": "#!/usr/bin/env bash\nset -euo pipefail\n\nif ${X_MODE:-false}; then\n set -x\nfi\n\n# =====================\n# paths\n# ======="
},
{
"path": "scripts/make",
"chars": 435,
"preview": "#!/usr/bin/env bash\nset -euo pipefail\n\n##? make install\n##? make uninstall\n\nexport NAVI_HOME=\"$(cd \"$(dirname \"$0\")/..\" "
},
{
"path": "scripts/release",
"chars": 4579,
"preview": "#!/usr/bin/env bash\nset -euo pipefail\n\n### -----------------------------------------------------------------------------"
},
{
"path": "scripts/test",
"chars": 155,
"preview": "#!/usr/bin/env bash\nset -euo pipefail\n\nexport NAVI_HOME=\"$(cd \"$(dirname \"$0\")/..\" && pwd)\"\nsource \"${NAVI_HOME}/scripts"
},
{
"path": "shell/navi.plugin.bash",
"chars": 984,
"preview": "#!/usr/bin/env bash\n\n_navi_call() {\n local result=\"$(navi \"$@\" </dev/tty)\"\n printf \"%s\" \"$result\"\n}\n\n_navi_widget() "
},
{
"path": "shell/navi.plugin.elv",
"chars": 704,
"preview": "use str\n\nfn call-navi {\n if (eq $edit:current-command '') {\n var answer = (navi --print)\n edit:replace-input $ans"
},
{
"path": "shell/navi.plugin.fish",
"chars": 1897,
"preview": "function _navi_smart_replace\n set --local query (commandline --current-process | string trim)\n set --local version"
},
{
"path": "shell/navi.plugin.nu",
"chars": 1008,
"preview": "export def navi_widget [] {\n let current_input = (commandline)\n let last_command = ($current_input | navi fn widge"
},
{
"path": "shell/navi.plugin.ps1",
"chars": 1895,
"preview": "\n\n$null = New-Module {\n\n function Invoke-Navi {\n $startArgs = @{\n FileName = \"navi\";\n Ar"
},
{
"path": "shell/navi.plugin.zsh",
"chars": 977,
"preview": "#!/usr/bin/env zsh\n\n_navi_call() {\n local result=\"$(navi \"$@\" </dev/tty)\"\n printf \"%s\" \"$result\"\n}\n\n_navi_widget() {"
},
{
"path": "src/bin/main.rs",
"chars": 1553,
"preview": "extern crate navi;\n\nuse crate::navi::prelude::*;\nuse thiserror::Error;\n\n#[derive(Error, Debug)]\n#[error(\n \"\\rHey, lis"
},
{
"path": "src/clients/cheatsh.rs",
"chars": 1769,
"preview": "use crate::prelude::*;\nuse std::process::Command;\n\nfn map_line(line: &str) -> String {\n line.trim().trim_end_matches("
},
{
"path": "src/clients/mod.rs",
"chars": 31,
"preview": "pub mod cheatsh;\npub mod tldr;\n"
},
{
"path": "src/clients/tldr.rs",
"chars": 3329,
"preview": "use crate::config::CONFIG;\nuse crate::prelude::*;\nuse std::process::{Command, Stdio};\n\nlazy_static! {\n pub static ref"
},
{
"path": "src/commands/core/actor.rs",
"chars": 8020,
"preview": "use crate::common::clipboard;\nuse crate::common::fs;\nuse crate::common::shell;\nuse crate::common::shell::ShellSpawnError"
},
{
"path": "src/commands/core/mod.rs",
"chars": 2165,
"preview": "mod actor;\n\nuse crate::clients::{cheatsh, tldr};\nuse crate::config::Source;\nuse crate::deser;\nuse crate::filesystem;\nuse"
},
{
"path": "src/commands/func/map.rs",
"chars": 297,
"preview": "use crate::common::shell::{self, ShellSpawnError};\nuse crate::prelude::*;\n\npub fn expand() -> Result<()> {\n let cmd ="
},
{
"path": "src/commands/func/mod.rs",
"chars": 1088,
"preview": "mod map;\nmod widget;\n\nuse super::core;\nuse super::temp;\nuse crate::common::url;\nuse crate::prelude::*;\nuse clap::Args;\nu"
},
{
"path": "src/commands/func/widget.rs",
"chars": 1234,
"preview": "use crate::prelude::*;\nuse std::io::{self, Read};\n\npub fn last_command() -> Result<()> {\n let mut text = String::new("
},
{
"path": "src/commands/info.rs",
"chars": 1819,
"preview": "use crate::filesystem;\nuse crate::prelude::*;\nuse clap::{Args, Subcommand};\n\n#[derive(Debug, Clone, Args)]\npub struct In"
},
{
"path": "src/commands/mod.rs",
"chars": 994,
"preview": "pub mod core;\npub mod func;\npub mod info;\npub mod preview;\npub mod repo;\npub mod shell;\npub mod temp;\n\nuse crate::comman"
},
{
"path": "src/commands/preview/mod.rs",
"chars": 1128,
"preview": "use crate::deser;\nuse crate::prelude::*;\nuse clap::Args;\nuse crossterm::style::{style, Stylize};\nuse std::process;\n\npub "
},
{
"path": "src/commands/preview/var.rs",
"chars": 3714,
"preview": "use crate::deser;\nuse crate::env_var;\nuse crate::finder;\nuse crate::prelude::*;\nuse clap::Args;\nuse crossterm::style::st"
},
{
"path": "src/commands/preview/var_stdin.rs",
"chars": 1102,
"preview": "use clap::Args;\n\nuse super::var;\nuse crate::common::shell::{self, ShellSpawnError, EOF};\nuse crate::prelude::*;\nuse std:"
},
{
"path": "src/commands/repo/add.rs",
"chars": 3258,
"preview": "use crate::common::git;\nuse crate::filesystem;\nuse crate::finder::structures::{Opts as FinderOpts, SuggestionType};\nuse "
},
{
"path": "src/commands/repo/browse.rs",
"chars": 1396,
"preview": "use crate::filesystem;\nuse crate::finder::structures::{Opts as FinderOpts, SuggestionType};\n\nuse crate::common::git;\nuse"
},
{
"path": "src/commands/repo/mod.rs",
"chars": 1176,
"preview": "use crate::commands;\nuse crate::prelude::*;\nuse clap::{Args, Subcommand};\n\npub mod add;\npub mod browse;\n\n#[derive(Debug,"
},
{
"path": "src/commands/shell.rs",
"chars": 1276,
"preview": "use std::fmt;\nuse std::fmt::Display;\n\nuse clap::Args;\n\nuse crate::common::shell::Shell;\nuse crate::prelude::*;\n\nimpl Dis"
},
{
"path": "src/commands/temp.rs",
"chars": 1742,
"preview": "use crate::commands::core::get_fetcher;\nuse crate::common::shell::{self, ShellSpawnError};\nuse crate::finder::structures"
},
{
"path": "src/common/clipboard.rs",
"chars": 657,
"preview": "use crate::common::shell::{self, ShellSpawnError, EOF};\nuse crate::prelude::*;\n\npub fn copy(text: String) -> Result<()> "
},
{
"path": "src/common/deps.rs",
"chars": 115,
"preview": "use crate::prelude::*;\n\npub trait HasDeps {\n fn deps(&self) -> HashSet<TypeId> {\n HashSet::new()\n }\n}\n"
},
{
"path": "src/common/fs.rs",
"chars": 2931,
"preview": "use crate::prelude::*;\nuse remove_dir_all::remove_dir_all;\nuse std::ffi::OsStr;\nuse std::fs::{self, create_dir_all, File"
},
{
"path": "src/common/git.rs",
"chars": 1831,
"preview": "use crate::common::shell::ShellSpawnError;\nuse crate::prelude::*;\nuse std::process::Command;\n\npub fn shallow_clone(uri: "
},
{
"path": "src/common/hash.rs",
"chars": 569,
"preview": "use std::hash::{Hash, Hasher};\n\nconst MAGIC_INIT: u64 = 0x811C_9DC5;\n\npub fn fnv<T: Hash>(x: &T) -> u64 {\n let mut ha"
},
{
"path": "src/common/mod.rs",
"chars": 118,
"preview": "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",
"chars": 1170,
"preview": "use crate::prelude::*;\nuse clap::ValueEnum;\nuse std::process::Command;\nuse thiserror::Error;\n\npub const EOF: &str = \"NAV"
},
{
"path": "src/common/terminal.rs",
"chars": 1642,
"preview": "use crate::prelude::*;\nuse crossterm::style;\nuse crossterm::terminal;\n\nuse std::process::Command;\n\nconst FALLBACK_WIDTH:"
},
{
"path": "src/common/url.rs",
"chars": 793,
"preview": "use crate::common::shell::{self, ShellSpawnError};\nuse crate::prelude::*;\nuse anyhow::Result;\nuse shell::EOF;\n\npub fn op"
},
{
"path": "src/config/cli.rs",
"chars": 4967,
"preview": "use crate::commands;\nuse crate::finder::FinderChoice;\n\nuse clap::{crate_version, Parser, Subcommand};\n\n#[derive(Debug, P"
},
{
"path": "src/config/env.rs",
"chars": 884,
"preview": "use crate::env_var;\nuse crate::finder::FinderChoice;\nuse crate::prelude::*;\n\n#[derive(Debug)]\npub struct EnvConfig {\n "
},
{
"path": "src/config/mod.rs",
"chars": 5833,
"preview": "mod cli;\nmod env;\nmod yaml;\n\nuse crate::commands::func::Func;\nuse crate::finder::FinderChoice;\nuse crate::prelude::debug"
},
{
"path": "src/config/yaml.rs",
"chars": 5344,
"preview": "use super::env::EnvConfig;\nuse crate::common::fs;\nuse crate::filesystem::default_config_pathbuf;\nuse crate::finder::Find"
},
{
"path": "src/deser/mod.rs",
"chars": 1326,
"preview": "use crate::prelude::*;\nuse unicode_width::UnicodeWidthStr;\n\npub mod raycast;\npub mod terminal;\n\nconst NEWLINE_ESCAPE_CHA"
},
{
"path": "src/deser/raycast.rs",
"chars": 1333,
"preview": "use super::*;\nuse crate::structures::item::Item;\n\nconst FIELD_SEP_ESCAPE_CHAR: char = '\\x16';\n\npub fn write(item: &Item)"
},
{
"path": "src/deser/terminal.rs",
"chars": 2619,
"preview": "use super::*;\nuse crate::common::terminal;\nuse crate::structures::item::Item;\nuse crossterm::style::{style, Stylize};\nus"
},
{
"path": "src/env_var.rs",
"chars": 1129,
"preview": "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:"
},
{
"path": "src/filesystem.rs",
"chars": 9784,
"preview": "pub use crate::common::fs::{create_dir, exe_string, read_lines, remove_dir};\nuse crate::env_var;\nuse crate::parser::Pars"
},
{
"path": "src/finder/mod.rs",
"chars": 7068,
"preview": "use crate::deser;\nuse crate::prelude::*;\nuse std::io::Write;\nuse std::process::{self, Output};\nuse std::process::{Comman"
},
{
"path": "src/finder/post.rs",
"chars": 5274,
"preview": "use crate::common::shell;\nuse crate::finder::structures::SuggestionType;\nuse crate::prelude::*;\nuse shell::EOF;\nuse std:"
},
{
"path": "src/finder/structures.rs",
"chars": 2439,
"preview": "use crate::filesystem;\nuse crate::prelude::*;\n\n#[derive(Debug, PartialEq, Clone)]\npub struct Opts {\n pub query: Optio"
},
{
"path": "src/lib.rs",
"chars": 304,
"preview": "#[macro_use]\nextern crate lazy_static;\n\nmod clients;\nmod commands;\nmod common;\nmod config;\nmod deser;\nmod env_var;\nmod f"
},
{
"path": "src/libs/dns_common/component.rs",
"chars": 180,
"preview": "use crate::prelude::*;\n\npub trait Component: Any + AsAny + Send + Sync {}\n\npub trait AsAny: Any {\n fn as_any(&self) -"
},
{
"path": "src/libs/dns_common/mod.rs",
"chars": 32,
"preview": "pub mod component;\nmod tracing;\n"
},
{
"path": "src/libs/dns_common/tracing.rs",
"chars": 174,
"preview": "use crate::prelude::*;\n\n#[derive(Deserialize, Serialize, Debug, Clone)]\n#[serde(deny_unknown_fields)]\npub struct Tracing"
},
{
"path": "src/parser.rs",
"chars": 11259,
"preview": "use crate::common::fs;\nuse crate::deser;\nuse crate::finder::structures::{Opts as FinderOpts, SuggestionType};\nuse crate:"
},
{
"path": "src/prelude.rs",
"chars": 1117,
"preview": "pub use crate::common::deps::HasDeps;\npub use crate::common::fs::ToStringExt;\npub use crate::config::CONFIG; // TODO\npub"
},
{
"path": "src/structures/cheat.rs",
"chars": 1738,
"preview": "use crate::common::hash::fnv;\nuse crate::finder::structures::Opts;\nuse crate::prelude::*;\n\npub type Suggestion = (String"
},
{
"path": "src/structures/fetcher.rs",
"chars": 544,
"preview": "use crate::parser::Parser;\nuse crate::prelude::*;\n\npub trait Fetcher {\n fn fetch(&self, parser: &mut Parser) -> Resul"
},
{
"path": "src/structures/item.rs",
"chars": 561,
"preview": "use crate::common::hash::fnv;\n\n#[derive(Default, Debug)]\npub struct Item {\n pub tags: String,\n pub comment: String"
},
{
"path": "src/structures/mod.rs",
"chars": 46,
"preview": "pub mod cheat;\npub mod fetcher;\npub mod item;\n"
},
{
"path": "src/welcome.rs",
"chars": 603,
"preview": "use crate::parser::Parser;\nuse crate::prelude::*;\nuse crate::structures::fetcher;\n\npub fn populate_cheatsheet(parser: &m"
},
{
"path": "tests/cheats/more_cases.cheat",
"chars": 2256,
"preview": "; 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# mu"
},
{
"path": "tests/cheats/ssh.cheat",
"chars": 145,
"preview": "% 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)\\nr"
},
{
"path": "tests/config.yaml",
"chars": 308,
"preview": "style:\n tag:\n color: cyan\n width_percentage: 26\n min_width: 20\n comment:\n color: yellow\n width_percenta"
},
{
"path": "tests/core.bash",
"chars": 1666,
"preview": "#!/usr/bin/env bash\n# vim: filetype=sh\n\nsource \"${NAVI_HOME}/scripts/install\"\n\nNEWLINE_CHAR=\"\\036\"\n\nPASSED=0\nFAILED=0\nSK"
},
{
"path": "tests/helpers.sh",
"chars": 72,
"preview": "#!/usr/local/bin/env bash\n\nmyhelperfn() {\n echo \"inside helper: $*\"\n}\n"
},
{
"path": "tests/no_prompt_cheats/cases.cheat",
"chars": 2127,
"preview": "; author: CI/CD\n\n% test, first\n\n# trivial case -> \"foo\"\necho \"foo\"\n\n# map with underscores -> \"_foo_\"\necho \"<map1>\"\n\n# e"
},
{
"path": "tests/no_prompt_cheats/one.cheat",
"chars": 68,
"preview": "; author: CI/CD\n\n% test, first\n\n# trivial case -> \"foo\"\necho \"foo\"\n\n"
},
{
"path": "tests/run",
"chars": 3965,
"preview": "#!/usr/bin/env bash\nset -euo pipefail\n\nexport NAVI_HOME=\"$(cd \"$(dirname \"$0\")/..\" && pwd)\"\nsource \"${NAVI_HOME}/tests/c"
},
{
"path": "tests/tests.rs",
"chars": 223,
"preview": "#[cfg(test)]\nmod tests {\n #[test]\n fn it_works() {\n //let _x = navi::handle_config(navi::config_from_iter(\n"
}
]
About this extraction
This page contains the full source code of the denisidoro/navi GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 109 files (208.8 KB), approximately 56.0k tokens, and a symbol index with 260 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.