Compare commits
5 Commits
ignore-0.4
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 9d164f4b2c | |||
| b60b31706a | |||
| 0994661424 | |||
| ad6ec1b4c5 | |||
|
|
cd1f981bea |
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -194,7 +194,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Print available short flags
|
- name: Print available short flags
|
||||||
shell: bash
|
shell: bash
|
||||||
run: ${{ env.CARGO }} test --bin rg ${{ env.TARGET_FLAGS }} flags::defs::tests::available_shorts -- --nocapture
|
run: ${{ env.CARGO }} test --bin rgs ${{ env.TARGET_FLAGS }} flags::defs::tests::available_shorts -- --nocapture
|
||||||
|
|
||||||
# Setup and compile on the wasm32-wasip1 target
|
# Setup and compile on the wasm32-wasip1 target
|
||||||
wasm:
|
wasm:
|
||||||
|
|||||||
40
.github/workflows/release.yml
vendored
40
.github/workflows/release.yml
vendored
@@ -178,9 +178,9 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
${{ env.CARGO }} build --verbose --profile release-lto --features pcre2 ${{ env.TARGET_FLAGS }}
|
${{ env.CARGO }} build --verbose --profile release-lto --features pcre2 ${{ env.TARGET_FLAGS }}
|
||||||
if [[ "${{ matrix.os }}" == windows-* ]]; then
|
if [[ "${{ matrix.os }}" == windows-* ]]; then
|
||||||
bin="target/${{ matrix.target }}/release-lto/rg.exe"
|
bin="target/${{ matrix.target }}/release-lto/rgs.exe"
|
||||||
else
|
else
|
||||||
bin="target/${{ matrix.target }}/release-lto/rg"
|
bin="target/${{ matrix.target }}/release-lto/rgs"
|
||||||
fi
|
fi
|
||||||
echo "BIN=$bin" >> $GITHUB_ENV
|
echo "BIN=$bin" >> $GITHUB_ENV
|
||||||
|
|
||||||
@@ -203,14 +203,14 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
version="${{ needs.create-release.outputs.version }}"
|
version="${{ needs.create-release.outputs.version }}"
|
||||||
echo "ARCHIVE=ripgrep-$version-${{ matrix.target }}" >> $GITHUB_ENV
|
echo "ARCHIVE=rgs-$version-${{ matrix.target }}" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Creating directory for archive
|
- name: Creating directory for archive
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
mkdir -p "$ARCHIVE"/{complete,doc}
|
mkdir -p "$ARCHIVE"/{complete,doc}
|
||||||
cp "$BIN" "$ARCHIVE"/
|
cp "$BIN" "$ARCHIVE"/
|
||||||
cp {README.md,COPYING,UNLICENSE,LICENSE-MIT} "$ARCHIVE"/
|
cp {README.md,README-ripgrep.md,COPYING,UNLICENSE,LICENSE-MIT} "$ARCHIVE"/
|
||||||
cp {CHANGELOG.md,FAQ.md,GUIDE.md} "$ARCHIVE"/doc/
|
cp {CHANGELOG.md,FAQ.md,GUIDE.md} "$ARCHIVE"/doc/
|
||||||
|
|
||||||
- name: Generate man page and completions (no emulation)
|
- name: Generate man page and completions (no emulation)
|
||||||
@@ -218,11 +218,11 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
"$BIN" --version
|
"$BIN" --version
|
||||||
"$BIN" --generate complete-bash > "$ARCHIVE/complete/rg.bash"
|
"$BIN" --generate complete-bash > "$ARCHIVE/complete/rgs.bash"
|
||||||
"$BIN" --generate complete-fish > "$ARCHIVE/complete/rg.fish"
|
"$BIN" --generate complete-fish > "$ARCHIVE/complete/rgs.fish"
|
||||||
"$BIN" --generate complete-powershell > "$ARCHIVE/complete/_rg.ps1"
|
"$BIN" --generate complete-powershell > "$ARCHIVE/complete/_rgs.ps1"
|
||||||
"$BIN" --generate complete-zsh > "$ARCHIVE/complete/_rg"
|
"$BIN" --generate complete-zsh > "$ARCHIVE/complete/_rgs"
|
||||||
"$BIN" --generate man > "$ARCHIVE/doc/rg.1"
|
"$BIN" --generate man > "$ARCHIVE/doc/rgs.1"
|
||||||
|
|
||||||
- name: Generate man page and completions (emulation)
|
- name: Generate man page and completions (emulation)
|
||||||
if: matrix.qemu != ''
|
if: matrix.qemu != ''
|
||||||
@@ -236,27 +236,27 @@ jobs:
|
|||||||
"$PWD/target:/target:Z" \
|
"$PWD/target:/target:Z" \
|
||||||
"ghcr.io/cross-rs/${{ matrix.target }}:main" \
|
"ghcr.io/cross-rs/${{ matrix.target }}:main" \
|
||||||
"${{ matrix.qemu }}" "/$BIN" \
|
"${{ matrix.qemu }}" "/$BIN" \
|
||||||
--generate complete-bash > "$ARCHIVE/complete/rg.bash"
|
--generate complete-bash > "$ARCHIVE/complete/rgs.bash"
|
||||||
docker run --rm -v \
|
docker run --rm -v \
|
||||||
"$PWD/target:/target:Z" \
|
"$PWD/target:/target:Z" \
|
||||||
"ghcr.io/cross-rs/${{ matrix.target }}:main" \
|
"ghcr.io/cross-rs/${{ matrix.target }}:main" \
|
||||||
"${{ matrix.qemu }}" "/$BIN" \
|
"${{ matrix.qemu }}" "/$BIN" \
|
||||||
--generate complete-fish > "$ARCHIVE/complete/rg.fish"
|
--generate complete-fish > "$ARCHIVE/complete/rgs.fish"
|
||||||
docker run --rm -v \
|
docker run --rm -v \
|
||||||
"$PWD/target:/target:Z" \
|
"$PWD/target:/target:Z" \
|
||||||
"ghcr.io/cross-rs/${{ matrix.target }}:main" \
|
"ghcr.io/cross-rs/${{ matrix.target }}:main" \
|
||||||
"${{ matrix.qemu }}" "/$BIN" \
|
"${{ matrix.qemu }}" "/$BIN" \
|
||||||
--generate complete-powershell > "$ARCHIVE/complete/_rg.ps1"
|
--generate complete-powershell > "$ARCHIVE/complete/_rgs.ps1"
|
||||||
docker run --rm -v \
|
docker run --rm -v \
|
||||||
"$PWD/target:/target:Z" \
|
"$PWD/target:/target:Z" \
|
||||||
"ghcr.io/cross-rs/${{ matrix.target }}:main" \
|
"ghcr.io/cross-rs/${{ matrix.target }}:main" \
|
||||||
"${{ matrix.qemu }}" "/$BIN" \
|
"${{ matrix.qemu }}" "/$BIN" \
|
||||||
--generate complete-zsh > "$ARCHIVE/complete/_rg"
|
--generate complete-zsh > "$ARCHIVE/complete/_rgs"
|
||||||
docker run --rm -v \
|
docker run --rm -v \
|
||||||
"$PWD/target:/target:Z" \
|
"$PWD/target:/target:Z" \
|
||||||
"ghcr.io/cross-rs/${{ matrix.target }}:main" \
|
"ghcr.io/cross-rs/${{ matrix.target }}:main" \
|
||||||
"${{ matrix.qemu }}" "/$BIN" \
|
"${{ matrix.qemu }}" "/$BIN" \
|
||||||
--generate man > "$ARCHIVE/doc/rg.1"
|
--generate man > "$ARCHIVE/doc/rgs.1"
|
||||||
|
|
||||||
- name: Build archive (Windows)
|
- name: Build archive (Windows)
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -325,7 +325,7 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
cargo build --target ${{ env.TARGET }}
|
cargo build --target ${{ env.TARGET }}
|
||||||
bin="target/${{ env.TARGET }}/debug/rg"
|
bin="target/${{ env.TARGET }}/debug/rgs"
|
||||||
echo "BIN=$bin" >> $GITHUB_ENV
|
echo "BIN=$bin" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Create deployment directory
|
- name: Create deployment directory
|
||||||
@@ -338,14 +338,14 @@ jobs:
|
|||||||
- name: Generate man page
|
- name: Generate man page
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
"$BIN" --generate man > "$DEPLOY_DIR/rg.1"
|
"$BIN" --generate man > "$DEPLOY_DIR/rgs.1"
|
||||||
|
|
||||||
- name: Generate shell completions
|
- name: Generate shell completions
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
"$BIN" --generate complete-bash > "$DEPLOY_DIR/rg.bash"
|
"$BIN" --generate complete-bash > "$DEPLOY_DIR/rgs.bash"
|
||||||
"$BIN" --generate complete-fish > "$DEPLOY_DIR/rg.fish"
|
"$BIN" --generate complete-fish > "$DEPLOY_DIR/rgs.fish"
|
||||||
"$BIN" --generate complete-zsh > "$DEPLOY_DIR/_rg"
|
"$BIN" --generate complete-zsh > "$DEPLOY_DIR/_rgs"
|
||||||
|
|
||||||
- name: Build release binary
|
- name: Build release binary
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -353,7 +353,7 @@ jobs:
|
|||||||
cargo deb --profile deb --target ${{ env.TARGET }}
|
cargo deb --profile deb --target ${{ env.TARGET }}
|
||||||
version="${{ needs.create-release.outputs.version }}"
|
version="${{ needs.create-release.outputs.version }}"
|
||||||
echo "DEB_DIR=target/${{ env.TARGET }}/debian" >> $GITHUB_ENV
|
echo "DEB_DIR=target/${{ env.TARGET }}/debian" >> $GITHUB_ENV
|
||||||
echo "DEB_NAME=ripgrep_$version-1_amd64.deb" >> $GITHUB_ENV
|
echo "DEB_NAME=rgs_$version-1_amd64.deb" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Create sha256 sum of deb file
|
- name: Create sha256 sum of deb file
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|||||||
24
Cargo.toml
24
Cargo.toml
@@ -1,7 +1,10 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "ripgrep"
|
name = "rgs"
|
||||||
version = "15.1.0" #:version
|
version = "0.1.0" #:version
|
||||||
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
authors = [
|
||||||
|
"Andrew Gallant <jamslam@gmail.com>",
|
||||||
|
"Peisong Xiao <peisong.xiao.xps@gmail.com>",
|
||||||
|
]
|
||||||
description = """
|
description = """
|
||||||
ripgrep is a line-oriented search tool that recursively searches the current
|
ripgrep is a line-oriented search tool that recursively searches the current
|
||||||
directory for a regex pattern while respecting gitignore rules. ripgrep has
|
directory for a regex pattern while respecting gitignore rules. ripgrep has
|
||||||
@@ -30,7 +33,7 @@ rust-version = "1.85"
|
|||||||
[[bin]]
|
[[bin]]
|
||||||
bench = false
|
bench = false
|
||||||
path = "crates/core/main.rs"
|
path = "crates/core/main.rs"
|
||||||
name = "rg"
|
name = "rgs"
|
||||||
|
|
||||||
[[test]]
|
[[test]]
|
||||||
name = "integration"
|
name = "integration"
|
||||||
@@ -93,25 +96,26 @@ inherits = "release-lto"
|
|||||||
features = ["pcre2"]
|
features = ["pcre2"]
|
||||||
section = "utils"
|
section = "utils"
|
||||||
assets = [
|
assets = [
|
||||||
["target/release/rg", "usr/bin/", "755"],
|
["target/release/rgs", "usr/bin/", "755"],
|
||||||
["COPYING", "usr/share/doc/ripgrep/", "644"],
|
["COPYING", "usr/share/doc/ripgrep/", "644"],
|
||||||
["LICENSE-MIT", "usr/share/doc/ripgrep/", "644"],
|
["LICENSE-MIT", "usr/share/doc/ripgrep/", "644"],
|
||||||
["UNLICENSE", "usr/share/doc/ripgrep/", "644"],
|
["UNLICENSE", "usr/share/doc/ripgrep/", "644"],
|
||||||
["CHANGELOG.md", "usr/share/doc/ripgrep/CHANGELOG", "644"],
|
["CHANGELOG.md", "usr/share/doc/ripgrep/CHANGELOG", "644"],
|
||||||
["README.md", "usr/share/doc/ripgrep/README", "644"],
|
["README.md", "usr/share/doc/ripgrep/README", "644"],
|
||||||
|
["README-ripgrep.md", "usr/share/doc/ripgrep/README-ripgrep", "644"],
|
||||||
["FAQ.md", "usr/share/doc/ripgrep/FAQ", "644"],
|
["FAQ.md", "usr/share/doc/ripgrep/FAQ", "644"],
|
||||||
# The man page is automatically generated by ripgrep's build process, so
|
# The man page is automatically generated by ripgrep's build process, so
|
||||||
# this file isn't actually committed. Instead, to create a dpkg, either
|
# this file isn't actually committed. Instead, to create a dpkg, either
|
||||||
# create a deployment/deb directory and copy the man page to it, or use the
|
# create a deployment/deb directory and copy the man page to it, or use the
|
||||||
# 'ci/build-deb' script.
|
# 'ci/build-deb' script.
|
||||||
["deployment/deb/rg.1", "usr/share/man/man1/rg.1", "644"],
|
["deployment/deb/rgs.1", "usr/share/man/man1/rgs.1", "644"],
|
||||||
# Similarly for shell completions.
|
# Similarly for shell completions.
|
||||||
["deployment/deb/rg.bash", "usr/share/bash-completion/completions/rg", "644"],
|
["deployment/deb/rgs.bash", "usr/share/bash-completion/completions/rgs", "644"],
|
||||||
["deployment/deb/rg.fish", "usr/share/fish/vendor_completions.d/rg.fish", "644"],
|
["deployment/deb/rgs.fish", "usr/share/fish/vendor_completions.d/rgs.fish", "644"],
|
||||||
["deployment/deb/_rg", "usr/share/zsh/vendor-completions/", "644"],
|
["deployment/deb/_rgs", "usr/share/zsh/vendor-completions/", "644"],
|
||||||
]
|
]
|
||||||
extended-description = """\
|
extended-description = """\
|
||||||
ripgrep (rg) recursively searches your current directory for a regex pattern.
|
rgs recursively searches your current directory for a regex pattern.
|
||||||
By default, ripgrep will respect your .gitignore and automatically skip hidden
|
By default, ripgrep will respect your .gitignore and automatically skip hidden
|
||||||
files/directories and binary files.
|
files/directories and binary files.
|
||||||
"""
|
"""
|
||||||
|
|||||||
30
GUIDE.md
30
GUIDE.md
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
This guide is intended to give an elementary description of ripgrep and an
|
This guide is intended to give an elementary description of ripgrep and an
|
||||||
overview of its capabilities. This guide assumes that ripgrep is
|
overview of its capabilities. This guide assumes that ripgrep is
|
||||||
[installed](README.md#installation)
|
[installed](README-ripgrep.md#installation)
|
||||||
and that readers have passing familiarity with using command line tools. This
|
and that readers have passing familiarity with using command line tools. This
|
||||||
also assumes a Unix-like system, although most commands are probably easily
|
also assumes a Unix-like system, although most commands are probably easily
|
||||||
translatable to any command line shell environment.
|
translatable to any command line shell environment.
|
||||||
@@ -42,17 +42,17 @@ $ unzip 0.7.1.zip
|
|||||||
$ cd ripgrep-0.7.1
|
$ cd ripgrep-0.7.1
|
||||||
$ ls
|
$ ls
|
||||||
benchsuite grep tests Cargo.toml LICENSE-MIT
|
benchsuite grep tests Cargo.toml LICENSE-MIT
|
||||||
ci ignore wincolor CHANGELOG.md README.md
|
ci ignore wincolor CHANGELOG.md README-ripgrep.md
|
||||||
complete pkg appveyor.yml compile snapcraft.yaml
|
complete pkg appveyor.yml compile snapcraft.yaml
|
||||||
doc src build.rs COPYING UNLICENSE
|
doc src build.rs COPYING UNLICENSE
|
||||||
globset termcolor Cargo.lock HomebrewFormula
|
globset termcolor Cargo.lock HomebrewFormula
|
||||||
```
|
```
|
||||||
|
|
||||||
Let's try our first search by looking for all occurrences of the word `fast`
|
Let's try our first search by looking for all occurrences of the word `fast`
|
||||||
in `README.md`:
|
in `README-ripgrep.md`:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ rg fast README.md
|
$ rg fast README-ripgrep.md
|
||||||
75: faster than both. (N.B. It is not, strictly speaking, a "drop-in" replacement
|
75: faster than both. (N.B. It is not, strictly speaking, a "drop-in" replacement
|
||||||
88: color and full Unicode support. Unlike GNU grep, `ripgrep` stays fast while
|
88: color and full Unicode support. Unlike GNU grep, `ripgrep` stays fast while
|
||||||
119:### Is it really faster than everything else?
|
119:### Is it really faster than everything else?
|
||||||
@@ -64,7 +64,7 @@ $ rg fast README.md
|
|||||||
search any files, then re-run ripgrep with the `--debug` flag. One likely cause
|
search any files, then re-run ripgrep with the `--debug` flag. One likely cause
|
||||||
of this is that you have a `*` rule in a `$HOME/.gitignore` file.)
|
of this is that you have a `*` rule in a `$HOME/.gitignore` file.)
|
||||||
|
|
||||||
So what happened here? ripgrep read the contents of `README.md`, and for each
|
So what happened here? ripgrep read the contents of `README-ripgrep.md`, and for each
|
||||||
line that contained `fast`, ripgrep printed it to your terminal. ripgrep also
|
line that contained `fast`, ripgrep printed it to your terminal. ripgrep also
|
||||||
included the line number for each line by default. If your terminal supports
|
included the line number for each line by default. If your terminal supports
|
||||||
colors, then your output might actually look something like this screenshot:
|
colors, then your output might actually look something like this screenshot:
|
||||||
@@ -79,7 +79,7 @@ what if we wanted to find all lines have a word that contains `fast` followed
|
|||||||
by some number of other letters?
|
by some number of other letters?
|
||||||
|
|
||||||
```
|
```
|
||||||
$ rg 'fast\w+' README.md
|
$ rg 'fast\w+' README-ripgrep.md
|
||||||
75: faster than both. (N.B. It is not, strictly speaking, a "drop-in" replacement
|
75: faster than both. (N.B. It is not, strictly speaking, a "drop-in" replacement
|
||||||
119:### Is it really faster than everything else?
|
119:### Is it really faster than everything else?
|
||||||
```
|
```
|
||||||
@@ -95,7 +95,7 @@ like `faster` will. `faste` would also match!
|
|||||||
Here's a different variation on this same theme:
|
Here's a different variation on this same theme:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ rg 'fast\w*' README.md
|
$ rg 'fast\w*' README-ripgrep.md
|
||||||
75: faster than both. (N.B. It is not, strictly speaking, a "drop-in" replacement
|
75: faster than both. (N.B. It is not, strictly speaking, a "drop-in" replacement
|
||||||
88: color and full Unicode support. Unlike GNU grep, `ripgrep` stays fast while
|
88: color and full Unicode support. Unlike GNU grep, `ripgrep` stays fast while
|
||||||
119:### Is it really faster than everything else?
|
119:### Is it really faster than everything else?
|
||||||
@@ -444,7 +444,7 @@ text with some other text. This is easiest to explain with an example. Remember
|
|||||||
when we searched for the word `fast` in ripgrep's README?
|
when we searched for the word `fast` in ripgrep's README?
|
||||||
|
|
||||||
```
|
```
|
||||||
$ rg fast README.md
|
$ rg fast README-ripgrep.md
|
||||||
75: faster than both. (N.B. It is not, strictly speaking, a "drop-in" replacement
|
75: faster than both. (N.B. It is not, strictly speaking, a "drop-in" replacement
|
||||||
88: color and full Unicode support. Unlike GNU grep, `ripgrep` stays fast while
|
88: color and full Unicode support. Unlike GNU grep, `ripgrep` stays fast while
|
||||||
119:### Is it really faster than everything else?
|
119:### Is it really faster than everything else?
|
||||||
@@ -456,7 +456,7 @@ What if we wanted to *replace* all occurrences of `fast` with `FAST`? That's
|
|||||||
easy with ripgrep's `--replace` flag:
|
easy with ripgrep's `--replace` flag:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ rg fast README.md --replace FAST
|
$ rg fast README-ripgrep.md --replace FAST
|
||||||
75: FASTer than both. (N.B. It is not, strictly speaking, a "drop-in" replacement
|
75: FASTer than both. (N.B. It is not, strictly speaking, a "drop-in" replacement
|
||||||
88: color and full Unicode support. Unlike GNU grep, `ripgrep` stays FAST while
|
88: color and full Unicode support. Unlike GNU grep, `ripgrep` stays FAST while
|
||||||
119:### Is it really FASTer than everything else?
|
119:### Is it really FASTer than everything else?
|
||||||
@@ -467,7 +467,7 @@ $ rg fast README.md --replace FAST
|
|||||||
or, more succinctly,
|
or, more succinctly,
|
||||||
|
|
||||||
```
|
```
|
||||||
$ rg fast README.md -r FAST
|
$ rg fast README-ripgrep.md -r FAST
|
||||||
[snip]
|
[snip]
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -476,7 +476,7 @@ in the output. If you instead wanted to replace an entire line of text, then
|
|||||||
you need to include the entire line in your match. For example:
|
you need to include the entire line in your match. For example:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ rg '^.*fast.*$' README.md -r FAST
|
$ rg '^.*fast.*$' README-ripgrep.md -r FAST
|
||||||
75:FAST
|
75:FAST
|
||||||
88:FAST
|
88:FAST
|
||||||
119:FAST
|
119:FAST
|
||||||
@@ -488,7 +488,7 @@ Alternatively, you can combine the `--only-matching` (or `-o` for short) with
|
|||||||
the `--replace` flag to achieve the same result:
|
the `--replace` flag to achieve the same result:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ rg fast README.md --only-matching --replace FAST
|
$ rg fast README-ripgrep.md --only-matching --replace FAST
|
||||||
75:FAST
|
75:FAST
|
||||||
88:FAST
|
88:FAST
|
||||||
119:FAST
|
119:FAST
|
||||||
@@ -499,7 +499,7 @@ $ rg fast README.md --only-matching --replace FAST
|
|||||||
or, more succinctly,
|
or, more succinctly,
|
||||||
|
|
||||||
```
|
```
|
||||||
$ rg fast README.md -or FAST
|
$ rg fast README-ripgrep.md -or FAST
|
||||||
[snip]
|
[snip]
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -512,7 +512,7 @@ group" (indicated by parentheses) so that we can reference it later in our
|
|||||||
replacement string. For example:
|
replacement string. For example:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ rg 'fast\s+(\w+)' README.md -r 'fast-$1'
|
$ rg 'fast\s+(\w+)' README-ripgrep.md -r 'fast-$1'
|
||||||
88: color and full Unicode support. Unlike GNU grep, `ripgrep` stays fast-while
|
88: color and full Unicode support. Unlike GNU grep, `ripgrep` stays fast-while
|
||||||
124:Summarizing, `ripgrep` is fast-because:
|
124:Summarizing, `ripgrep` is fast-because:
|
||||||
```
|
```
|
||||||
@@ -528,7 +528,7 @@ using the indices. For example, the following command is equivalent to the
|
|||||||
above command:
|
above command:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ rg 'fast\s+(?P<word>\w+)' README.md -r 'fast-$word'
|
$ rg 'fast\s+(?P<word>\w+)' README-ripgrep.md -r 'fast-$word'
|
||||||
88: color and full Unicode support. Unlike GNU grep, `ripgrep` stays fast-while
|
88: color and full Unicode support. Unlike GNU grep, `ripgrep` stays fast-while
|
||||||
124:Summarizing, `ripgrep` is fast-because:
|
124:Summarizing, `ripgrep` is fast-because:
|
||||||
```
|
```
|
||||||
|
|||||||
541
README-ripgrep.md
Normal file
541
README-ripgrep.md
Normal file
@@ -0,0 +1,541 @@
|
|||||||
|
ripgrep (rg)
|
||||||
|
------------
|
||||||
|
ripgrep is a line-oriented search tool that recursively searches the current
|
||||||
|
directory for a regex pattern. By default, ripgrep will respect gitignore rules
|
||||||
|
and automatically skip hidden files/directories and binary files. (To disable
|
||||||
|
all automatic filtering by default, use `rg -uuu`.) ripgrep has first class
|
||||||
|
support on Windows, macOS and Linux, with binary downloads available for [every
|
||||||
|
release](https://github.com/BurntSushi/ripgrep/releases). ripgrep is similar to
|
||||||
|
other popular search tools like The Silver Searcher, ack and grep.
|
||||||
|
|
||||||
|
[](https://github.com/BurntSushi/ripgrep/actions)
|
||||||
|
[](https://crates.io/crates/ripgrep)
|
||||||
|
[](https://repology.org/project/ripgrep/badges)
|
||||||
|
|
||||||
|
Dual-licensed under MIT or the [UNLICENSE](https://unlicense.org).
|
||||||
|
|
||||||
|
|
||||||
|
### CHANGELOG
|
||||||
|
|
||||||
|
Please see the [CHANGELOG](CHANGELOG.md) for a release history.
|
||||||
|
|
||||||
|
### Documentation quick links
|
||||||
|
|
||||||
|
* [Installation](#installation)
|
||||||
|
* [User Guide](GUIDE.md)
|
||||||
|
* [Frequently Asked Questions](FAQ.md)
|
||||||
|
* [Regex syntax](https://docs.rs/regex/1/regex/#syntax)
|
||||||
|
* [Configuration files](GUIDE.md#configuration-file)
|
||||||
|
* [Shell completions](FAQ.md#complete)
|
||||||
|
* [Building](#building)
|
||||||
|
* [Translations](#translations)
|
||||||
|
|
||||||
|
|
||||||
|
### Screenshot of search results
|
||||||
|
|
||||||
|
[](https://burntsushi.net/stuff/ripgrep1.png)
|
||||||
|
|
||||||
|
|
||||||
|
### Quick examples comparing tools
|
||||||
|
|
||||||
|
This example searches the entire
|
||||||
|
[Linux kernel source tree](https://github.com/BurntSushi/linux)
|
||||||
|
(after running `make defconfig && make -j8`) for `[A-Z]+_SUSPEND`, where
|
||||||
|
all matches must be words. Timings were collected on a system with an Intel
|
||||||
|
i9-12900K 5.2 GHz.
|
||||||
|
|
||||||
|
Please remember that a single benchmark is never enough! See my
|
||||||
|
[blog post on ripgrep](https://blog.burntsushi.net/ripgrep/)
|
||||||
|
for a very detailed comparison with more benchmarks and analysis.
|
||||||
|
|
||||||
|
| Tool | Command | Line count | Time |
|
||||||
|
| ---- | ------- | ---------- | ---- |
|
||||||
|
| ripgrep (Unicode) | `rg -n -w '[A-Z]+_SUSPEND'` | 536 | **0.082s** (1.00x) |
|
||||||
|
| [hypergrep](https://github.com/p-ranav/hypergrep) | `hgrep -n -w '[A-Z]+_SUSPEND'` | 536 | 0.167s (2.04x) |
|
||||||
|
| [git grep](https://www.kernel.org/pub/software/scm/git/docs/git-grep.html) | `git grep -P -n -w '[A-Z]+_SUSPEND'` | 536 | 0.273s (3.34x) |
|
||||||
|
| [The Silver Searcher](https://github.com/ggreer/the_silver_searcher) | `ag -w '[A-Z]+_SUSPEND'` | 534 | 0.443s (5.43x) |
|
||||||
|
| [ugrep](https://github.com/Genivia/ugrep) | `ugrep -r --ignore-files --no-hidden -I -w '[A-Z]+_SUSPEND'` | 536 | 0.639s (7.82x) |
|
||||||
|
| [git grep](https://www.kernel.org/pub/software/scm/git/docs/git-grep.html) | `LC_ALL=C git grep -E -n -w '[A-Z]+_SUSPEND'` | 536 | 0.727s (8.91x) |
|
||||||
|
| [git grep (Unicode)](https://www.kernel.org/pub/software/scm/git/docs/git-grep.html) | `LC_ALL=en_US.UTF-8 git grep -E -n -w '[A-Z]+_SUSPEND'` | 536 | 2.670s (32.70x) |
|
||||||
|
| [ack](https://github.com/beyondgrep/ack3) | `ack -w '[A-Z]+_SUSPEND'` | 2677 | 2.935s (35.94x) |
|
||||||
|
|
||||||
|
Here's another benchmark on the same corpus as above that disregards gitignore
|
||||||
|
files and searches with a whitelist instead. The corpus is the same as in the
|
||||||
|
previous benchmark, and the flags passed to each command ensure that they are
|
||||||
|
doing equivalent work:
|
||||||
|
|
||||||
|
| Tool | Command | Line count | Time |
|
||||||
|
| ---- | ------- | ---------- | ---- |
|
||||||
|
| ripgrep | `rg -uuu -tc -n -w '[A-Z]+_SUSPEND'` | 447 | **0.063s** (1.00x) |
|
||||||
|
| [ugrep](https://github.com/Genivia/ugrep) | `ugrep -r -n --include='*.c' --include='*.h' -w '[A-Z]+_SUSPEND'` | 447 | 0.607s (9.62x) |
|
||||||
|
| [GNU grep](https://www.gnu.org/software/grep/) | `grep -E -r -n --include='*.c' --include='*.h' -w '[A-Z]+_SUSPEND'` | 447 | 0.674s (10.69x) |
|
||||||
|
|
||||||
|
Now we'll move to searching on single large file. Here is a straight-up
|
||||||
|
comparison between ripgrep, ugrep and GNU grep on a file cached in memory
|
||||||
|
(~13GB, [`OpenSubtitles.raw.en.gz`](http://opus.nlpl.eu/download.php?f=OpenSubtitles/v2018/mono/OpenSubtitles.raw.en.gz), decompressed):
|
||||||
|
|
||||||
|
| Tool | Command | Line count | Time |
|
||||||
|
| ---- | ------- | ---------- | ---- |
|
||||||
|
| ripgrep (Unicode) | `rg -w 'Sherlock [A-Z]\w+'` | 7882 | **1.042s** (1.00x) |
|
||||||
|
| [ugrep](https://github.com/Genivia/ugrep) | `ugrep -w 'Sherlock [A-Z]\w+'` | 7882 | 1.339s (1.28x) |
|
||||||
|
| [GNU grep (Unicode)](https://www.gnu.org/software/grep/) | `LC_ALL=en_US.UTF-8 egrep -w 'Sherlock [A-Z]\w+'` | 7882 | 6.577s (6.31x) |
|
||||||
|
|
||||||
|
In the above benchmark, passing the `-n` flag (for showing line numbers)
|
||||||
|
increases the times to `1.664s` for ripgrep and `9.484s` for GNU grep. ugrep
|
||||||
|
times are unaffected by the presence or absence of `-n`.
|
||||||
|
|
||||||
|
Beware of performance cliffs though:
|
||||||
|
|
||||||
|
| Tool | Command | Line count | Time |
|
||||||
|
| ---- | ------- | ---------- | ---- |
|
||||||
|
| ripgrep (Unicode) | `rg -w '[A-Z]\w+ Sherlock [A-Z]\w+'` | 485 | **1.053s** (1.00x) |
|
||||||
|
| [GNU grep (Unicode)](https://www.gnu.org/software/grep/) | `LC_ALL=en_US.UTF-8 grep -E -w '[A-Z]\w+ Sherlock [A-Z]\w+'` | 485 | 6.234s (5.92x) |
|
||||||
|
| [ugrep](https://github.com/Genivia/ugrep) | `ugrep -w '[A-Z]\w+ Sherlock [A-Z]\w+'` | 485 | 28.973s (27.51x) |
|
||||||
|
|
||||||
|
And performance can drop precipitously across the board when searching big
|
||||||
|
files for patterns without any opportunities for literal optimizations:
|
||||||
|
|
||||||
|
| Tool | Command | Line count | Time |
|
||||||
|
| ---- | ------- | ---------- | ---- |
|
||||||
|
| ripgrep | `rg '[A-Za-z]{30}'` | 6749 | **15.569s** (1.00x) |
|
||||||
|
| [ugrep](https://github.com/Genivia/ugrep) | `ugrep -E '[A-Za-z]{30}'` | 6749 | 21.857s (1.40x) |
|
||||||
|
| [GNU grep](https://www.gnu.org/software/grep/) | `LC_ALL=C grep -E '[A-Za-z]{30}'` | 6749 | 32.409s (2.08x) |
|
||||||
|
| [GNU grep (Unicode)](https://www.gnu.org/software/grep/) | `LC_ALL=en_US.UTF-8 grep -E '[A-Za-z]{30}'` | 6795 | 8m30s (32.74x) |
|
||||||
|
|
||||||
|
Finally, high match counts also tend to both tank performance and smooth
|
||||||
|
out the differences between tools (because performance is dominated by how
|
||||||
|
quickly one can handle a match and not the algorithm used to detect the match,
|
||||||
|
generally speaking):
|
||||||
|
|
||||||
|
| Tool | Command | Line count | Time |
|
||||||
|
| ---- | ------- | ---------- | ---- |
|
||||||
|
| ripgrep | `rg the` | 83499915 | **6.948s** (1.00x) |
|
||||||
|
| [ugrep](https://github.com/Genivia/ugrep) | `ugrep the` | 83499915 | 11.721s (1.69x) |
|
||||||
|
| [GNU grep](https://www.gnu.org/software/grep/) | `LC_ALL=C grep the` | 83499915 | 15.217s (2.19x) |
|
||||||
|
|
||||||
|
### Why should I use ripgrep?
|
||||||
|
|
||||||
|
* It can replace many use cases served by other search tools
|
||||||
|
because it contains most of their features and is generally faster. (See
|
||||||
|
[the FAQ](FAQ.md#posix4ever) for more details on whether ripgrep can truly
|
||||||
|
replace grep.)
|
||||||
|
* Like other tools specialized to code search, ripgrep defaults to
|
||||||
|
[recursive search](GUIDE.md#recursive-search) and does [automatic
|
||||||
|
filtering](GUIDE.md#automatic-filtering). Namely, ripgrep won't search files
|
||||||
|
ignored by your `.gitignore`/`.ignore`/`.rgignore` files, it won't search
|
||||||
|
hidden files and it won't search binary files. Automatic filtering can be
|
||||||
|
disabled with `rg -uuu`.
|
||||||
|
* ripgrep can [search specific types of files](GUIDE.md#manual-filtering-file-types).
|
||||||
|
For example, `rg -tpy foo` limits your search to Python files and `rg -Tjs
|
||||||
|
foo` excludes JavaScript files from your search. ripgrep can be taught about
|
||||||
|
new file types with custom matching rules.
|
||||||
|
* ripgrep supports many features found in `grep`, such as showing the context
|
||||||
|
of search results, searching multiple patterns, highlighting matches with
|
||||||
|
color and full Unicode support. Unlike GNU grep, ripgrep stays fast while
|
||||||
|
supporting Unicode (which is always on).
|
||||||
|
* ripgrep has optional support for switching its regex engine to use PCRE2.
|
||||||
|
Among other things, this makes it possible to use look-around and
|
||||||
|
backreferences in your patterns, which are not supported in ripgrep's default
|
||||||
|
regex engine. PCRE2 support can be enabled with `-P/--pcre2` (use PCRE2
|
||||||
|
always) or `--auto-hybrid-regex` (use PCRE2 only if needed). An alternative
|
||||||
|
syntax is provided via the `--engine (default|pcre2|auto)` option.
|
||||||
|
* ripgrep has [rudimentary support for replacements](GUIDE.md#replacements),
|
||||||
|
which permit rewriting output based on what was matched.
|
||||||
|
* ripgrep supports [searching files in text encodings](GUIDE.md#file-encoding)
|
||||||
|
other than UTF-8, such as UTF-16, latin-1, GBK, EUC-JP, Shift_JIS and more.
|
||||||
|
(Some support for automatically detecting UTF-16 is provided. Other text
|
||||||
|
encodings must be specifically specified with the `-E/--encoding` flag.)
|
||||||
|
* ripgrep supports searching files compressed in a common format (brotli,
|
||||||
|
bzip2, gzip, lz4, lzma, xz, or zstandard) with the `-z/--search-zip` flag.
|
||||||
|
* ripgrep supports
|
||||||
|
[arbitrary input preprocessing filters](GUIDE.md#preprocessor)
|
||||||
|
which could be PDF text extraction, less supported decompression, decrypting,
|
||||||
|
automatic encoding detection and so on.
|
||||||
|
* ripgrep can be configured via a
|
||||||
|
[configuration file](GUIDE.md#configuration-file).
|
||||||
|
|
||||||
|
In other words, use ripgrep if you like speed, filtering by default, fewer
|
||||||
|
bugs and Unicode support.
|
||||||
|
|
||||||
|
|
||||||
|
### Why shouldn't I use ripgrep?
|
||||||
|
|
||||||
|
Despite initially not wanting to add every feature under the sun to ripgrep,
|
||||||
|
over time, ripgrep has grown support for most features found in other file
|
||||||
|
searching tools. This includes searching for results spanning across multiple
|
||||||
|
lines, and opt-in support for PCRE2, which provides look-around and
|
||||||
|
backreference support.
|
||||||
|
|
||||||
|
At this point, the primary reasons not to use ripgrep probably consist of one
|
||||||
|
or more of the following:
|
||||||
|
|
||||||
|
* You need a portable and ubiquitous tool. While ripgrep works on Windows,
|
||||||
|
macOS and Linux, it is not ubiquitous and it does not conform to any
|
||||||
|
standard such as POSIX. The best tool for this job is good old grep.
|
||||||
|
* There still exists some other feature (or bug) not listed in this README that
|
||||||
|
you rely on that's in another tool that isn't in ripgrep.
|
||||||
|
* There is a performance edge case where ripgrep doesn't do well where another
|
||||||
|
tool does do well. (Please file a bug report!)
|
||||||
|
* ripgrep isn't possible to install on your machine or isn't available for your
|
||||||
|
platform. (Please file a bug report!)
|
||||||
|
|
||||||
|
|
||||||
|
### Is it really faster than everything else?
|
||||||
|
|
||||||
|
Generally, yes. A large number of benchmarks with detailed analysis for each is
|
||||||
|
[available on my blog](https://blog.burntsushi.net/ripgrep/).
|
||||||
|
|
||||||
|
Summarizing, ripgrep is fast because:
|
||||||
|
|
||||||
|
* It is built on top of
|
||||||
|
[Rust's regex engine](https://github.com/rust-lang/regex).
|
||||||
|
Rust's regex engine uses finite automata, SIMD and aggressive literal
|
||||||
|
optimizations to make searching very fast. (PCRE2 support can be opted into
|
||||||
|
with the `-P/--pcre2` flag.)
|
||||||
|
* Rust's regex library maintains performance with full Unicode support by
|
||||||
|
building UTF-8 decoding directly into its deterministic finite automaton
|
||||||
|
engine.
|
||||||
|
* It supports searching with either memory maps or by searching incrementally
|
||||||
|
with an intermediate buffer. The former is better for single files and the
|
||||||
|
latter is better for large directories. ripgrep chooses the best searching
|
||||||
|
strategy for you automatically.
|
||||||
|
* Applies your ignore patterns in `.gitignore` files using a
|
||||||
|
[`RegexSet`](https://docs.rs/regex/1/regex/struct.RegexSet.html).
|
||||||
|
That means a single file path can be matched against multiple glob patterns
|
||||||
|
simultaneously.
|
||||||
|
* It uses a lock-free parallel recursive directory iterator, courtesy of
|
||||||
|
[`crossbeam`](https://docs.rs/crossbeam) and
|
||||||
|
[`ignore`](https://docs.rs/ignore).
|
||||||
|
|
||||||
|
|
||||||
|
### Feature comparison
|
||||||
|
|
||||||
|
Andy Lester, author of [ack](https://beyondgrep.com/), has published an
|
||||||
|
excellent table comparing the features of ack, ag, git-grep, GNU grep and
|
||||||
|
ripgrep: https://beyondgrep.com/feature-comparison/
|
||||||
|
|
||||||
|
Note that ripgrep has grown a few significant new features recently that
|
||||||
|
are not yet present in Andy's table. This includes, but is not limited to,
|
||||||
|
configuration files, passthru, support for searching compressed files,
|
||||||
|
multiline search and opt-in fancy regex support via PCRE2.
|
||||||
|
|
||||||
|
|
||||||
|
### Playground
|
||||||
|
|
||||||
|
If you'd like to try ripgrep before installing, there's an unofficial
|
||||||
|
[playground](https://codapi.org/ripgrep/) and an [interactive
|
||||||
|
tutorial](https://codapi.org/try/ripgrep/).
|
||||||
|
|
||||||
|
If you have any questions about these, please open an issue in the [tutorial
|
||||||
|
repo](https://github.com/nalgeon/tryxinyminutes).
|
||||||
|
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
The binary name for ripgrep is `rg`.
|
||||||
|
|
||||||
|
**[Archives of precompiled binaries for ripgrep are available for Windows,
|
||||||
|
macOS and Linux.](https://github.com/BurntSushi/ripgrep/releases)** Linux and
|
||||||
|
Windows binaries are static executables. Users of platforms not explicitly
|
||||||
|
mentioned below are advised to download one of these archives.
|
||||||
|
|
||||||
|
If you're a **macOS Homebrew** or a **Linuxbrew** user, then you can install
|
||||||
|
ripgrep from homebrew-core:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ brew install ripgrep
|
||||||
|
```
|
||||||
|
|
||||||
|
If you're a **MacPorts** user, then you can install ripgrep from the
|
||||||
|
[official ports](https://www.macports.org/ports.php?by=name&substr=ripgrep):
|
||||||
|
|
||||||
|
```
|
||||||
|
$ sudo port install ripgrep
|
||||||
|
```
|
||||||
|
|
||||||
|
If you're a **Windows Chocolatey** user, then you can install ripgrep from the
|
||||||
|
[official repo](https://chocolatey.org/packages/ripgrep):
|
||||||
|
|
||||||
|
```
|
||||||
|
$ choco install ripgrep
|
||||||
|
```
|
||||||
|
|
||||||
|
If you're a **Windows Scoop** user, then you can install ripgrep from the
|
||||||
|
[official bucket](https://github.com/ScoopInstaller/Main/blob/master/bucket/ripgrep.json):
|
||||||
|
|
||||||
|
```
|
||||||
|
$ scoop install ripgrep
|
||||||
|
```
|
||||||
|
|
||||||
|
If you're a **Windows Winget** user, then you can install ripgrep from the
|
||||||
|
[winget-pkgs](https://github.com/microsoft/winget-pkgs/tree/master/manifests/b/BurntSushi/ripgrep)
|
||||||
|
repository:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ winget install BurntSushi.ripgrep.MSVC
|
||||||
|
```
|
||||||
|
|
||||||
|
If you're an **Arch Linux** user, then you can install ripgrep from the official repos:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ sudo pacman -S ripgrep
|
||||||
|
```
|
||||||
|
|
||||||
|
If you're a **Gentoo** user, you can install ripgrep from the
|
||||||
|
[official repo](https://packages.gentoo.org/packages/sys-apps/ripgrep):
|
||||||
|
|
||||||
|
```
|
||||||
|
$ sudo emerge sys-apps/ripgrep
|
||||||
|
```
|
||||||
|
|
||||||
|
If you're a **Fedora** user, you can install ripgrep from official
|
||||||
|
repositories.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ sudo dnf install ripgrep
|
||||||
|
```
|
||||||
|
|
||||||
|
If you're an **openSUSE** user, ripgrep is included in **openSUSE Tumbleweed**
|
||||||
|
and **openSUSE Leap** since 15.1.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ sudo zypper install ripgrep
|
||||||
|
```
|
||||||
|
|
||||||
|
If you're a **CentOS Stream 10** user, you can install ripgrep from the
|
||||||
|
[EPEL](https://docs.fedoraproject.org/en-US/epel/getting-started/) repository:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ sudo dnf config-manager --set-enabled crb
|
||||||
|
$ sudo dnf install https://dl.fedoraproject.org/pub/epel/epel-release-latest-10.noarch.rpm
|
||||||
|
$ sudo dnf install ripgrep
|
||||||
|
```
|
||||||
|
|
||||||
|
If you're a **Red Hat 10** user, you can install ripgrep from the
|
||||||
|
[EPEL](https://docs.fedoraproject.org/en-US/epel/getting-started/) repository:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ sudo subscription-manager repos --enable codeready-builder-for-rhel-10-$(arch)-rpms
|
||||||
|
$ sudo dnf install https://dl.fedoraproject.org/pub/epel/epel-release-latest-10.noarch.rpm
|
||||||
|
$ sudo dnf install ripgrep
|
||||||
|
```
|
||||||
|
|
||||||
|
If you're a **Rocky Linux 10** user, you can install ripgrep from the
|
||||||
|
[EPEL](https://docs.fedoraproject.org/en-US/epel/getting-started/) repository:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ sudo dnf install https://dl.fedoraproject.org/pub/epel/epel-release-latest-10.noarch.rpm
|
||||||
|
$ sudo dnf install ripgrep
|
||||||
|
```
|
||||||
|
|
||||||
|
If you're a **Nix** user, you can install ripgrep from
|
||||||
|
[nixpkgs](https://github.com/NixOS/nixpkgs/blob/master/pkgs/by-name/ri/ripgrep/package.nix):
|
||||||
|
|
||||||
|
```
|
||||||
|
$ nix-env --install ripgrep
|
||||||
|
```
|
||||||
|
|
||||||
|
If you're a **Flox** user, you can install ripgrep as follows:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ flox install ripgrep
|
||||||
|
```
|
||||||
|
|
||||||
|
If you're a **Guix** user, you can install ripgrep from the official
|
||||||
|
package collection:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ guix install ripgrep
|
||||||
|
```
|
||||||
|
|
||||||
|
If you're a **Debian** user (or a user of a Debian derivative like **Ubuntu**),
|
||||||
|
then ripgrep can be installed using a binary `.deb` file provided in each
|
||||||
|
[ripgrep release](https://github.com/BurntSushi/ripgrep/releases).
|
||||||
|
|
||||||
|
```
|
||||||
|
$ curl -LO https://github.com/BurntSushi/ripgrep/releases/download/14.1.1/ripgrep_14.1.1-1_amd64.deb
|
||||||
|
$ sudo dpkg -i ripgrep_14.1.1-1_amd64.deb
|
||||||
|
```
|
||||||
|
|
||||||
|
If you run Debian stable, ripgrep is [officially maintained by
|
||||||
|
Debian](https://tracker.debian.org/pkg/rust-ripgrep), although its version may
|
||||||
|
be older than the `deb` package available in the previous step.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ sudo apt-get install ripgrep
|
||||||
|
```
|
||||||
|
|
||||||
|
If you're an **Ubuntu Cosmic (18.10)** (or newer) user, ripgrep is
|
||||||
|
[available](https://launchpad.net/ubuntu/+source/rust-ripgrep) using the same
|
||||||
|
packaging as Debian:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ sudo apt-get install ripgrep
|
||||||
|
```
|
||||||
|
|
||||||
|
(N.B. Various snaps for ripgrep on Ubuntu are also available, but none of them
|
||||||
|
seem to work right and generate a number of very strange bug reports that I
|
||||||
|
don't know how to fix and don't have the time to fix. Therefore, it is no
|
||||||
|
longer a recommended installation option.)
|
||||||
|
|
||||||
|
If you're an **ALT** user, you can install ripgrep from the
|
||||||
|
[official repo](https://packages.altlinux.org/en/search?name=ripgrep):
|
||||||
|
|
||||||
|
```
|
||||||
|
$ sudo apt-get install ripgrep
|
||||||
|
```
|
||||||
|
|
||||||
|
If you're a **FreeBSD** user, then you can install ripgrep from the
|
||||||
|
[official ports](https://www.freshports.org/textproc/ripgrep/):
|
||||||
|
|
||||||
|
```
|
||||||
|
$ sudo pkg install ripgrep
|
||||||
|
```
|
||||||
|
|
||||||
|
If you're an **OpenBSD** user, then you can install ripgrep from the
|
||||||
|
[official ports](https://openports.se/textproc/ripgrep):
|
||||||
|
|
||||||
|
```
|
||||||
|
$ doas pkg_add ripgrep
|
||||||
|
```
|
||||||
|
|
||||||
|
If you're a **NetBSD** user, then you can install ripgrep from
|
||||||
|
[pkgsrc](https://pkgsrc.se/textproc/ripgrep):
|
||||||
|
|
||||||
|
```
|
||||||
|
$ sudo pkgin install ripgrep
|
||||||
|
```
|
||||||
|
|
||||||
|
If you're a **Haiku x86_64** user, then you can install ripgrep from the
|
||||||
|
[official ports](https://github.com/haikuports/haikuports/tree/master/sys-apps/ripgrep):
|
||||||
|
|
||||||
|
```
|
||||||
|
$ sudo pkgman install ripgrep
|
||||||
|
```
|
||||||
|
|
||||||
|
If you're a **Haiku x86_gcc2** user, then you can install ripgrep from the
|
||||||
|
same port as Haiku x86_64 using the x86 secondary architecture build:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ sudo pkgman install ripgrep_x86
|
||||||
|
```
|
||||||
|
|
||||||
|
If you're a **Void Linux** user, then you can install ripgrep from the
|
||||||
|
[official repository](https://voidlinux.org/packages/?arch=x86_64&q=ripgrep):
|
||||||
|
|
||||||
|
```
|
||||||
|
$ sudo xbps-install -Syv ripgrep
|
||||||
|
```
|
||||||
|
|
||||||
|
If you're a **Rust programmer**, ripgrep can be installed with `cargo`.
|
||||||
|
|
||||||
|
* Note that the minimum supported version of Rust for ripgrep is **1.85.0**,
|
||||||
|
although ripgrep may work with older versions.
|
||||||
|
* Note that the binary may be bigger than expected because it contains debug
|
||||||
|
symbols. This is intentional. To remove debug symbols and therefore reduce
|
||||||
|
the file size, run `strip` on the binary.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ cargo install ripgrep
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively, one can use [`cargo
|
||||||
|
binstall`](https://github.com/cargo-bins/cargo-binstall) to install a ripgrep
|
||||||
|
binary directly from GitHub:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ cargo binstall ripgrep
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Building
|
||||||
|
|
||||||
|
ripgrep is written in Rust, so you'll need to grab a
|
||||||
|
[Rust installation](https://www.rust-lang.org/) in order to compile it.
|
||||||
|
ripgrep compiles with Rust 1.85.0 (stable) or newer. In general, ripgrep tracks
|
||||||
|
the latest stable release of the Rust compiler.
|
||||||
|
|
||||||
|
To build ripgrep:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ git clone https://github.com/BurntSushi/ripgrep
|
||||||
|
$ cd ripgrep
|
||||||
|
$ cargo build --release
|
||||||
|
$ ./target/release/rg --version
|
||||||
|
0.1.3
|
||||||
|
```
|
||||||
|
|
||||||
|
**NOTE:** In the past, ripgrep supported a `simd-accel` Cargo feature when
|
||||||
|
using a Rust nightly compiler. This only benefited UTF-16 transcoding.
|
||||||
|
Since it required unstable features, this build mode was prone to breakage.
|
||||||
|
Because of that, support for it has been removed. If you want SIMD
|
||||||
|
optimizations for UTF-16 transcoding, then you'll have to petition the
|
||||||
|
[`encoding_rs`](https://github.com/hsivonen/encoding_rs) project to use stable
|
||||||
|
APIs.
|
||||||
|
|
||||||
|
Finally, optional PCRE2 support can be built with ripgrep by enabling the
|
||||||
|
`pcre2` feature:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ cargo build --release --features 'pcre2'
|
||||||
|
```
|
||||||
|
|
||||||
|
Enabling the PCRE2 feature works with a stable Rust compiler and will
|
||||||
|
attempt to automatically find and link with your system's PCRE2 library via
|
||||||
|
`pkg-config`. If one doesn't exist, then ripgrep will build PCRE2 from source
|
||||||
|
using your system's C compiler and then statically link it into the final
|
||||||
|
executable. Static linking can be forced even when there is an available PCRE2
|
||||||
|
system library by either building ripgrep with the MUSL target or by setting
|
||||||
|
`PCRE2_SYS_STATIC=1`.
|
||||||
|
|
||||||
|
ripgrep can be built with the MUSL target on Linux by first installing the MUSL
|
||||||
|
library on your system (consult your friendly neighborhood package manager).
|
||||||
|
Then you just need to add MUSL support to your Rust toolchain and rebuild
|
||||||
|
ripgrep, which yields a fully static executable:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ rustup target add x86_64-unknown-linux-musl
|
||||||
|
$ cargo build --release --target x86_64-unknown-linux-musl
|
||||||
|
```
|
||||||
|
|
||||||
|
Applying the `--features` flag from above works as expected. If you want to
|
||||||
|
build a static executable with MUSL and with PCRE2, then you will need to have
|
||||||
|
`musl-gcc` installed, which might be in a separate package from the actual
|
||||||
|
MUSL library, depending on your Linux distribution.
|
||||||
|
|
||||||
|
|
||||||
|
### Running tests
|
||||||
|
|
||||||
|
ripgrep is relatively well-tested, including both unit tests and integration
|
||||||
|
tests. To run the full test suite, use:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ cargo test --all
|
||||||
|
```
|
||||||
|
|
||||||
|
from the repository root.
|
||||||
|
|
||||||
|
|
||||||
|
### Related tools
|
||||||
|
|
||||||
|
* [delta](https://github.com/dandavison/delta) is a syntax highlighting
|
||||||
|
pager that supports the `rg --json` output format. So all you need to do to
|
||||||
|
make it work is `rg --json pattern | delta`. See [delta's manual section on
|
||||||
|
grep](https://dandavison.github.io/delta/grep.html) for more details.
|
||||||
|
|
||||||
|
|
||||||
|
### Vulnerability reporting
|
||||||
|
|
||||||
|
For reporting a security vulnerability, please
|
||||||
|
[contact Andrew Gallant](https://blog.burntsushi.net/about/).
|
||||||
|
The contact page has my email address and PGP public key if you wish to send an
|
||||||
|
encrypted message.
|
||||||
|
|
||||||
|
|
||||||
|
### Translations
|
||||||
|
|
||||||
|
The following is a list of known translations of ripgrep's documentation. These
|
||||||
|
are unofficially maintained and may not be up to date.
|
||||||
|
|
||||||
|
* [Chinese](https://github.com/chinanf-boy/ripgrep-zh#%E6%9B%B4%E6%96%B0-)
|
||||||
|
* [Spanish](https://github.com/UltiRequiem/traducciones/tree/master/ripgrep)
|
||||||
557
README.md
557
README.md
@@ -1,541 +1,42 @@
|
|||||||
ripgrep (rg)
|
# rgs
|
||||||
------------
|
|
||||||
ripgrep is a line-oriented search tool that recursively searches the current
|
|
||||||
directory for a regex pattern. By default, ripgrep will respect gitignore rules
|
|
||||||
and automatically skip hidden files/directories and binary files. (To disable
|
|
||||||
all automatic filtering by default, use `rg -uuu`.) ripgrep has first class
|
|
||||||
support on Windows, macOS and Linux, with binary downloads available for [every
|
|
||||||
release](https://github.com/BurntSushi/ripgrep/releases). ripgrep is similar to
|
|
||||||
other popular search tools like The Silver Searcher, ack and grep.
|
|
||||||
|
|
||||||
[](https://github.com/BurntSushi/ripgrep/actions)
|
This repository is a fork of ripgrep with additional features. The original
|
||||||
[](https://crates.io/crates/ripgrep)
|
ripgrep documentation is in README-ripgrep.md:
|
||||||
[](https://repology.org/project/ripgrep/badges)
|
|
||||||
|
|
||||||
Dual-licensed under MIT or the [UNLICENSE](https://unlicense.org).
|
- README-ripgrep.md
|
||||||
|
|
||||||
|
## Additional features in this fork
|
||||||
|
|
||||||
### CHANGELOG
|
### Multiline windowing
|
||||||
|
|
||||||
Please see the [CHANGELOG](CHANGELOG.md) for a release history.
|
- `--multiline-window=N` (short: `-W N`) limits multiline matches to a sliding
|
||||||
|
window of N lines while still using multiline matching semantics.
|
||||||
|
- `--multiline-window` implicitly enables `--multiline` and cannot be used with
|
||||||
|
`--no-multiline`.
|
||||||
|
|
||||||
### Documentation quick links
|
### Per-file match indexing
|
||||||
|
|
||||||
* [Installation](#installation)
|
- `--in-file-index` / `--no-in-file-index` control indexing of matches within a
|
||||||
* [User Guide](GUIDE.md)
|
file to disambiguate overlapping multiline results.
|
||||||
* [Frequently Asked Questions](FAQ.md)
|
- When enabled, output is formatted as `filename[index]:line:`.
|
||||||
* [Regex syntax](https://docs.rs/regex/1/regex/#syntax)
|
- When searching a single file, the output is formatted as `[index]:line:` (no
|
||||||
* [Configuration files](GUIDE.md#configuration-file)
|
filename).
|
||||||
* [Shell completions](FAQ.md#complete)
|
|
||||||
* [Building](#building)
|
|
||||||
* [Translations](#translations)
|
|
||||||
|
|
||||||
|
### Squashed output
|
||||||
|
|
||||||
### Screenshot of search results
|
- `--squash` collapses contiguous Unicode whitespace (including newlines) into a
|
||||||
|
single ASCII space in output.
|
||||||
|
- `--squash-nl-only` collapses newlines into spaces while preserving other
|
||||||
|
whitespace.
|
||||||
|
- When multiple lines are squashed into one, line numbers are printed as
|
||||||
|
`start-end:`.
|
||||||
|
|
||||||
[](https://burntsushi.net/stuff/ripgrep1.png)
|
### Binary name
|
||||||
|
|
||||||
|
- The target binary name is `rgs` (not `rg`).
|
||||||
|
|
||||||
### Quick examples comparing tools
|
## Acknowledgements
|
||||||
|
|
||||||
This example searches the entire
|
This project is built on top of ripgrep by Andrew Gallant and contributors.
|
||||||
[Linux kernel source tree](https://github.com/BurntSushi/linux)
|
All credit for the original tool, documentation, and design belongs to the
|
||||||
(after running `make defconfig && make -j8`) for `[A-Z]+_SUSPEND`, where
|
ripgrep project. See README-ripgrep.md and the upstream licenses for details.
|
||||||
all matches must be words. Timings were collected on a system with an Intel
|
|
||||||
i9-12900K 5.2 GHz.
|
|
||||||
|
|
||||||
Please remember that a single benchmark is never enough! See my
|
|
||||||
[blog post on ripgrep](https://blog.burntsushi.net/ripgrep/)
|
|
||||||
for a very detailed comparison with more benchmarks and analysis.
|
|
||||||
|
|
||||||
| Tool | Command | Line count | Time |
|
|
||||||
| ---- | ------- | ---------- | ---- |
|
|
||||||
| ripgrep (Unicode) | `rg -n -w '[A-Z]+_SUSPEND'` | 536 | **0.082s** (1.00x) |
|
|
||||||
| [hypergrep](https://github.com/p-ranav/hypergrep) | `hgrep -n -w '[A-Z]+_SUSPEND'` | 536 | 0.167s (2.04x) |
|
|
||||||
| [git grep](https://www.kernel.org/pub/software/scm/git/docs/git-grep.html) | `git grep -P -n -w '[A-Z]+_SUSPEND'` | 536 | 0.273s (3.34x) |
|
|
||||||
| [The Silver Searcher](https://github.com/ggreer/the_silver_searcher) | `ag -w '[A-Z]+_SUSPEND'` | 534 | 0.443s (5.43x) |
|
|
||||||
| [ugrep](https://github.com/Genivia/ugrep) | `ugrep -r --ignore-files --no-hidden -I -w '[A-Z]+_SUSPEND'` | 536 | 0.639s (7.82x) |
|
|
||||||
| [git grep](https://www.kernel.org/pub/software/scm/git/docs/git-grep.html) | `LC_ALL=C git grep -E -n -w '[A-Z]+_SUSPEND'` | 536 | 0.727s (8.91x) |
|
|
||||||
| [git grep (Unicode)](https://www.kernel.org/pub/software/scm/git/docs/git-grep.html) | `LC_ALL=en_US.UTF-8 git grep -E -n -w '[A-Z]+_SUSPEND'` | 536 | 2.670s (32.70x) |
|
|
||||||
| [ack](https://github.com/beyondgrep/ack3) | `ack -w '[A-Z]+_SUSPEND'` | 2677 | 2.935s (35.94x) |
|
|
||||||
|
|
||||||
Here's another benchmark on the same corpus as above that disregards gitignore
|
|
||||||
files and searches with a whitelist instead. The corpus is the same as in the
|
|
||||||
previous benchmark, and the flags passed to each command ensure that they are
|
|
||||||
doing equivalent work:
|
|
||||||
|
|
||||||
| Tool | Command | Line count | Time |
|
|
||||||
| ---- | ------- | ---------- | ---- |
|
|
||||||
| ripgrep | `rg -uuu -tc -n -w '[A-Z]+_SUSPEND'` | 447 | **0.063s** (1.00x) |
|
|
||||||
| [ugrep](https://github.com/Genivia/ugrep) | `ugrep -r -n --include='*.c' --include='*.h' -w '[A-Z]+_SUSPEND'` | 447 | 0.607s (9.62x) |
|
|
||||||
| [GNU grep](https://www.gnu.org/software/grep/) | `grep -E -r -n --include='*.c' --include='*.h' -w '[A-Z]+_SUSPEND'` | 447 | 0.674s (10.69x) |
|
|
||||||
|
|
||||||
Now we'll move to searching on single large file. Here is a straight-up
|
|
||||||
comparison between ripgrep, ugrep and GNU grep on a file cached in memory
|
|
||||||
(~13GB, [`OpenSubtitles.raw.en.gz`](http://opus.nlpl.eu/download.php?f=OpenSubtitles/v2018/mono/OpenSubtitles.raw.en.gz), decompressed):
|
|
||||||
|
|
||||||
| Tool | Command | Line count | Time |
|
|
||||||
| ---- | ------- | ---------- | ---- |
|
|
||||||
| ripgrep (Unicode) | `rg -w 'Sherlock [A-Z]\w+'` | 7882 | **1.042s** (1.00x) |
|
|
||||||
| [ugrep](https://github.com/Genivia/ugrep) | `ugrep -w 'Sherlock [A-Z]\w+'` | 7882 | 1.339s (1.28x) |
|
|
||||||
| [GNU grep (Unicode)](https://www.gnu.org/software/grep/) | `LC_ALL=en_US.UTF-8 egrep -w 'Sherlock [A-Z]\w+'` | 7882 | 6.577s (6.31x) |
|
|
||||||
|
|
||||||
In the above benchmark, passing the `-n` flag (for showing line numbers)
|
|
||||||
increases the times to `1.664s` for ripgrep and `9.484s` for GNU grep. ugrep
|
|
||||||
times are unaffected by the presence or absence of `-n`.
|
|
||||||
|
|
||||||
Beware of performance cliffs though:
|
|
||||||
|
|
||||||
| Tool | Command | Line count | Time |
|
|
||||||
| ---- | ------- | ---------- | ---- |
|
|
||||||
| ripgrep (Unicode) | `rg -w '[A-Z]\w+ Sherlock [A-Z]\w+'` | 485 | **1.053s** (1.00x) |
|
|
||||||
| [GNU grep (Unicode)](https://www.gnu.org/software/grep/) | `LC_ALL=en_US.UTF-8 grep -E -w '[A-Z]\w+ Sherlock [A-Z]\w+'` | 485 | 6.234s (5.92x) |
|
|
||||||
| [ugrep](https://github.com/Genivia/ugrep) | `ugrep -w '[A-Z]\w+ Sherlock [A-Z]\w+'` | 485 | 28.973s (27.51x) |
|
|
||||||
|
|
||||||
And performance can drop precipitously across the board when searching big
|
|
||||||
files for patterns without any opportunities for literal optimizations:
|
|
||||||
|
|
||||||
| Tool | Command | Line count | Time |
|
|
||||||
| ---- | ------- | ---------- | ---- |
|
|
||||||
| ripgrep | `rg '[A-Za-z]{30}'` | 6749 | **15.569s** (1.00x) |
|
|
||||||
| [ugrep](https://github.com/Genivia/ugrep) | `ugrep -E '[A-Za-z]{30}'` | 6749 | 21.857s (1.40x) |
|
|
||||||
| [GNU grep](https://www.gnu.org/software/grep/) | `LC_ALL=C grep -E '[A-Za-z]{30}'` | 6749 | 32.409s (2.08x) |
|
|
||||||
| [GNU grep (Unicode)](https://www.gnu.org/software/grep/) | `LC_ALL=en_US.UTF-8 grep -E '[A-Za-z]{30}'` | 6795 | 8m30s (32.74x) |
|
|
||||||
|
|
||||||
Finally, high match counts also tend to both tank performance and smooth
|
|
||||||
out the differences between tools (because performance is dominated by how
|
|
||||||
quickly one can handle a match and not the algorithm used to detect the match,
|
|
||||||
generally speaking):
|
|
||||||
|
|
||||||
| Tool | Command | Line count | Time |
|
|
||||||
| ---- | ------- | ---------- | ---- |
|
|
||||||
| ripgrep | `rg the` | 83499915 | **6.948s** (1.00x) |
|
|
||||||
| [ugrep](https://github.com/Genivia/ugrep) | `ugrep the` | 83499915 | 11.721s (1.69x) |
|
|
||||||
| [GNU grep](https://www.gnu.org/software/grep/) | `LC_ALL=C grep the` | 83499915 | 15.217s (2.19x) |
|
|
||||||
|
|
||||||
### Why should I use ripgrep?
|
|
||||||
|
|
||||||
* It can replace many use cases served by other search tools
|
|
||||||
because it contains most of their features and is generally faster. (See
|
|
||||||
[the FAQ](FAQ.md#posix4ever) for more details on whether ripgrep can truly
|
|
||||||
replace grep.)
|
|
||||||
* Like other tools specialized to code search, ripgrep defaults to
|
|
||||||
[recursive search](GUIDE.md#recursive-search) and does [automatic
|
|
||||||
filtering](GUIDE.md#automatic-filtering). Namely, ripgrep won't search files
|
|
||||||
ignored by your `.gitignore`/`.ignore`/`.rgignore` files, it won't search
|
|
||||||
hidden files and it won't search binary files. Automatic filtering can be
|
|
||||||
disabled with `rg -uuu`.
|
|
||||||
* ripgrep can [search specific types of files](GUIDE.md#manual-filtering-file-types).
|
|
||||||
For example, `rg -tpy foo` limits your search to Python files and `rg -Tjs
|
|
||||||
foo` excludes JavaScript files from your search. ripgrep can be taught about
|
|
||||||
new file types with custom matching rules.
|
|
||||||
* ripgrep supports many features found in `grep`, such as showing the context
|
|
||||||
of search results, searching multiple patterns, highlighting matches with
|
|
||||||
color and full Unicode support. Unlike GNU grep, ripgrep stays fast while
|
|
||||||
supporting Unicode (which is always on).
|
|
||||||
* ripgrep has optional support for switching its regex engine to use PCRE2.
|
|
||||||
Among other things, this makes it possible to use look-around and
|
|
||||||
backreferences in your patterns, which are not supported in ripgrep's default
|
|
||||||
regex engine. PCRE2 support can be enabled with `-P/--pcre2` (use PCRE2
|
|
||||||
always) or `--auto-hybrid-regex` (use PCRE2 only if needed). An alternative
|
|
||||||
syntax is provided via the `--engine (default|pcre2|auto)` option.
|
|
||||||
* ripgrep has [rudimentary support for replacements](GUIDE.md#replacements),
|
|
||||||
which permit rewriting output based on what was matched.
|
|
||||||
* ripgrep supports [searching files in text encodings](GUIDE.md#file-encoding)
|
|
||||||
other than UTF-8, such as UTF-16, latin-1, GBK, EUC-JP, Shift_JIS and more.
|
|
||||||
(Some support for automatically detecting UTF-16 is provided. Other text
|
|
||||||
encodings must be specifically specified with the `-E/--encoding` flag.)
|
|
||||||
* ripgrep supports searching files compressed in a common format (brotli,
|
|
||||||
bzip2, gzip, lz4, lzma, xz, or zstandard) with the `-z/--search-zip` flag.
|
|
||||||
* ripgrep supports
|
|
||||||
[arbitrary input preprocessing filters](GUIDE.md#preprocessor)
|
|
||||||
which could be PDF text extraction, less supported decompression, decrypting,
|
|
||||||
automatic encoding detection and so on.
|
|
||||||
* ripgrep can be configured via a
|
|
||||||
[configuration file](GUIDE.md#configuration-file).
|
|
||||||
|
|
||||||
In other words, use ripgrep if you like speed, filtering by default, fewer
|
|
||||||
bugs and Unicode support.
|
|
||||||
|
|
||||||
|
|
||||||
### Why shouldn't I use ripgrep?
|
|
||||||
|
|
||||||
Despite initially not wanting to add every feature under the sun to ripgrep,
|
|
||||||
over time, ripgrep has grown support for most features found in other file
|
|
||||||
searching tools. This includes searching for results spanning across multiple
|
|
||||||
lines, and opt-in support for PCRE2, which provides look-around and
|
|
||||||
backreference support.
|
|
||||||
|
|
||||||
At this point, the primary reasons not to use ripgrep probably consist of one
|
|
||||||
or more of the following:
|
|
||||||
|
|
||||||
* You need a portable and ubiquitous tool. While ripgrep works on Windows,
|
|
||||||
macOS and Linux, it is not ubiquitous and it does not conform to any
|
|
||||||
standard such as POSIX. The best tool for this job is good old grep.
|
|
||||||
* There still exists some other feature (or bug) not listed in this README that
|
|
||||||
you rely on that's in another tool that isn't in ripgrep.
|
|
||||||
* There is a performance edge case where ripgrep doesn't do well where another
|
|
||||||
tool does do well. (Please file a bug report!)
|
|
||||||
* ripgrep isn't possible to install on your machine or isn't available for your
|
|
||||||
platform. (Please file a bug report!)
|
|
||||||
|
|
||||||
|
|
||||||
### Is it really faster than everything else?
|
|
||||||
|
|
||||||
Generally, yes. A large number of benchmarks with detailed analysis for each is
|
|
||||||
[available on my blog](https://blog.burntsushi.net/ripgrep/).
|
|
||||||
|
|
||||||
Summarizing, ripgrep is fast because:
|
|
||||||
|
|
||||||
* It is built on top of
|
|
||||||
[Rust's regex engine](https://github.com/rust-lang/regex).
|
|
||||||
Rust's regex engine uses finite automata, SIMD and aggressive literal
|
|
||||||
optimizations to make searching very fast. (PCRE2 support can be opted into
|
|
||||||
with the `-P/--pcre2` flag.)
|
|
||||||
* Rust's regex library maintains performance with full Unicode support by
|
|
||||||
building UTF-8 decoding directly into its deterministic finite automaton
|
|
||||||
engine.
|
|
||||||
* It supports searching with either memory maps or by searching incrementally
|
|
||||||
with an intermediate buffer. The former is better for single files and the
|
|
||||||
latter is better for large directories. ripgrep chooses the best searching
|
|
||||||
strategy for you automatically.
|
|
||||||
* Applies your ignore patterns in `.gitignore` files using a
|
|
||||||
[`RegexSet`](https://docs.rs/regex/1/regex/struct.RegexSet.html).
|
|
||||||
That means a single file path can be matched against multiple glob patterns
|
|
||||||
simultaneously.
|
|
||||||
* It uses a lock-free parallel recursive directory iterator, courtesy of
|
|
||||||
[`crossbeam`](https://docs.rs/crossbeam) and
|
|
||||||
[`ignore`](https://docs.rs/ignore).
|
|
||||||
|
|
||||||
|
|
||||||
### Feature comparison
|
|
||||||
|
|
||||||
Andy Lester, author of [ack](https://beyondgrep.com/), has published an
|
|
||||||
excellent table comparing the features of ack, ag, git-grep, GNU grep and
|
|
||||||
ripgrep: https://beyondgrep.com/feature-comparison/
|
|
||||||
|
|
||||||
Note that ripgrep has grown a few significant new features recently that
|
|
||||||
are not yet present in Andy's table. This includes, but is not limited to,
|
|
||||||
configuration files, passthru, support for searching compressed files,
|
|
||||||
multiline search and opt-in fancy regex support via PCRE2.
|
|
||||||
|
|
||||||
|
|
||||||
### Playground
|
|
||||||
|
|
||||||
If you'd like to try ripgrep before installing, there's an unofficial
|
|
||||||
[playground](https://codapi.org/ripgrep/) and an [interactive
|
|
||||||
tutorial](https://codapi.org/try/ripgrep/).
|
|
||||||
|
|
||||||
If you have any questions about these, please open an issue in the [tutorial
|
|
||||||
repo](https://github.com/nalgeon/tryxinyminutes).
|
|
||||||
|
|
||||||
|
|
||||||
### Installation
|
|
||||||
|
|
||||||
The binary name for ripgrep is `rg`.
|
|
||||||
|
|
||||||
**[Archives of precompiled binaries for ripgrep are available for Windows,
|
|
||||||
macOS and Linux.](https://github.com/BurntSushi/ripgrep/releases)** Linux and
|
|
||||||
Windows binaries are static executables. Users of platforms not explicitly
|
|
||||||
mentioned below are advised to download one of these archives.
|
|
||||||
|
|
||||||
If you're a **macOS Homebrew** or a **Linuxbrew** user, then you can install
|
|
||||||
ripgrep from homebrew-core:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ brew install ripgrep
|
|
||||||
```
|
|
||||||
|
|
||||||
If you're a **MacPorts** user, then you can install ripgrep from the
|
|
||||||
[official ports](https://www.macports.org/ports.php?by=name&substr=ripgrep):
|
|
||||||
|
|
||||||
```
|
|
||||||
$ sudo port install ripgrep
|
|
||||||
```
|
|
||||||
|
|
||||||
If you're a **Windows Chocolatey** user, then you can install ripgrep from the
|
|
||||||
[official repo](https://chocolatey.org/packages/ripgrep):
|
|
||||||
|
|
||||||
```
|
|
||||||
$ choco install ripgrep
|
|
||||||
```
|
|
||||||
|
|
||||||
If you're a **Windows Scoop** user, then you can install ripgrep from the
|
|
||||||
[official bucket](https://github.com/ScoopInstaller/Main/blob/master/bucket/ripgrep.json):
|
|
||||||
|
|
||||||
```
|
|
||||||
$ scoop install ripgrep
|
|
||||||
```
|
|
||||||
|
|
||||||
If you're a **Windows Winget** user, then you can install ripgrep from the
|
|
||||||
[winget-pkgs](https://github.com/microsoft/winget-pkgs/tree/master/manifests/b/BurntSushi/ripgrep)
|
|
||||||
repository:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ winget install BurntSushi.ripgrep.MSVC
|
|
||||||
```
|
|
||||||
|
|
||||||
If you're an **Arch Linux** user, then you can install ripgrep from the official repos:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ sudo pacman -S ripgrep
|
|
||||||
```
|
|
||||||
|
|
||||||
If you're a **Gentoo** user, you can install ripgrep from the
|
|
||||||
[official repo](https://packages.gentoo.org/packages/sys-apps/ripgrep):
|
|
||||||
|
|
||||||
```
|
|
||||||
$ sudo emerge sys-apps/ripgrep
|
|
||||||
```
|
|
||||||
|
|
||||||
If you're a **Fedora** user, you can install ripgrep from official
|
|
||||||
repositories.
|
|
||||||
|
|
||||||
```
|
|
||||||
$ sudo dnf install ripgrep
|
|
||||||
```
|
|
||||||
|
|
||||||
If you're an **openSUSE** user, ripgrep is included in **openSUSE Tumbleweed**
|
|
||||||
and **openSUSE Leap** since 15.1.
|
|
||||||
|
|
||||||
```
|
|
||||||
$ sudo zypper install ripgrep
|
|
||||||
```
|
|
||||||
|
|
||||||
If you're a **CentOS Stream 10** user, you can install ripgrep from the
|
|
||||||
[EPEL](https://docs.fedoraproject.org/en-US/epel/getting-started/) repository:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ sudo dnf config-manager --set-enabled crb
|
|
||||||
$ sudo dnf install https://dl.fedoraproject.org/pub/epel/epel-release-latest-10.noarch.rpm
|
|
||||||
$ sudo dnf install ripgrep
|
|
||||||
```
|
|
||||||
|
|
||||||
If you're a **Red Hat 10** user, you can install ripgrep from the
|
|
||||||
[EPEL](https://docs.fedoraproject.org/en-US/epel/getting-started/) repository:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ sudo subscription-manager repos --enable codeready-builder-for-rhel-10-$(arch)-rpms
|
|
||||||
$ sudo dnf install https://dl.fedoraproject.org/pub/epel/epel-release-latest-10.noarch.rpm
|
|
||||||
$ sudo dnf install ripgrep
|
|
||||||
```
|
|
||||||
|
|
||||||
If you're a **Rocky Linux 10** user, you can install ripgrep from the
|
|
||||||
[EPEL](https://docs.fedoraproject.org/en-US/epel/getting-started/) repository:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ sudo dnf install https://dl.fedoraproject.org/pub/epel/epel-release-latest-10.noarch.rpm
|
|
||||||
$ sudo dnf install ripgrep
|
|
||||||
```
|
|
||||||
|
|
||||||
If you're a **Nix** user, you can install ripgrep from
|
|
||||||
[nixpkgs](https://github.com/NixOS/nixpkgs/blob/master/pkgs/by-name/ri/ripgrep/package.nix):
|
|
||||||
|
|
||||||
```
|
|
||||||
$ nix-env --install ripgrep
|
|
||||||
```
|
|
||||||
|
|
||||||
If you're a **Flox** user, you can install ripgrep as follows:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ flox install ripgrep
|
|
||||||
```
|
|
||||||
|
|
||||||
If you're a **Guix** user, you can install ripgrep from the official
|
|
||||||
package collection:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ guix install ripgrep
|
|
||||||
```
|
|
||||||
|
|
||||||
If you're a **Debian** user (or a user of a Debian derivative like **Ubuntu**),
|
|
||||||
then ripgrep can be installed using a binary `.deb` file provided in each
|
|
||||||
[ripgrep release](https://github.com/BurntSushi/ripgrep/releases).
|
|
||||||
|
|
||||||
```
|
|
||||||
$ curl -LO https://github.com/BurntSushi/ripgrep/releases/download/14.1.1/ripgrep_14.1.1-1_amd64.deb
|
|
||||||
$ sudo dpkg -i ripgrep_14.1.1-1_amd64.deb
|
|
||||||
```
|
|
||||||
|
|
||||||
If you run Debian stable, ripgrep is [officially maintained by
|
|
||||||
Debian](https://tracker.debian.org/pkg/rust-ripgrep), although its version may
|
|
||||||
be older than the `deb` package available in the previous step.
|
|
||||||
|
|
||||||
```
|
|
||||||
$ sudo apt-get install ripgrep
|
|
||||||
```
|
|
||||||
|
|
||||||
If you're an **Ubuntu Cosmic (18.10)** (or newer) user, ripgrep is
|
|
||||||
[available](https://launchpad.net/ubuntu/+source/rust-ripgrep) using the same
|
|
||||||
packaging as Debian:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ sudo apt-get install ripgrep
|
|
||||||
```
|
|
||||||
|
|
||||||
(N.B. Various snaps for ripgrep on Ubuntu are also available, but none of them
|
|
||||||
seem to work right and generate a number of very strange bug reports that I
|
|
||||||
don't know how to fix and don't have the time to fix. Therefore, it is no
|
|
||||||
longer a recommended installation option.)
|
|
||||||
|
|
||||||
If you're an **ALT** user, you can install ripgrep from the
|
|
||||||
[official repo](https://packages.altlinux.org/en/search?name=ripgrep):
|
|
||||||
|
|
||||||
```
|
|
||||||
$ sudo apt-get install ripgrep
|
|
||||||
```
|
|
||||||
|
|
||||||
If you're a **FreeBSD** user, then you can install ripgrep from the
|
|
||||||
[official ports](https://www.freshports.org/textproc/ripgrep/):
|
|
||||||
|
|
||||||
```
|
|
||||||
$ sudo pkg install ripgrep
|
|
||||||
```
|
|
||||||
|
|
||||||
If you're an **OpenBSD** user, then you can install ripgrep from the
|
|
||||||
[official ports](https://openports.se/textproc/ripgrep):
|
|
||||||
|
|
||||||
```
|
|
||||||
$ doas pkg_add ripgrep
|
|
||||||
```
|
|
||||||
|
|
||||||
If you're a **NetBSD** user, then you can install ripgrep from
|
|
||||||
[pkgsrc](https://pkgsrc.se/textproc/ripgrep):
|
|
||||||
|
|
||||||
```
|
|
||||||
$ sudo pkgin install ripgrep
|
|
||||||
```
|
|
||||||
|
|
||||||
If you're a **Haiku x86_64** user, then you can install ripgrep from the
|
|
||||||
[official ports](https://github.com/haikuports/haikuports/tree/master/sys-apps/ripgrep):
|
|
||||||
|
|
||||||
```
|
|
||||||
$ sudo pkgman install ripgrep
|
|
||||||
```
|
|
||||||
|
|
||||||
If you're a **Haiku x86_gcc2** user, then you can install ripgrep from the
|
|
||||||
same port as Haiku x86_64 using the x86 secondary architecture build:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ sudo pkgman install ripgrep_x86
|
|
||||||
```
|
|
||||||
|
|
||||||
If you're a **Void Linux** user, then you can install ripgrep from the
|
|
||||||
[official repository](https://voidlinux.org/packages/?arch=x86_64&q=ripgrep):
|
|
||||||
|
|
||||||
```
|
|
||||||
$ sudo xbps-install -Syv ripgrep
|
|
||||||
```
|
|
||||||
|
|
||||||
If you're a **Rust programmer**, ripgrep can be installed with `cargo`.
|
|
||||||
|
|
||||||
* Note that the minimum supported version of Rust for ripgrep is **1.85.0**,
|
|
||||||
although ripgrep may work with older versions.
|
|
||||||
* Note that the binary may be bigger than expected because it contains debug
|
|
||||||
symbols. This is intentional. To remove debug symbols and therefore reduce
|
|
||||||
the file size, run `strip` on the binary.
|
|
||||||
|
|
||||||
```
|
|
||||||
$ cargo install ripgrep
|
|
||||||
```
|
|
||||||
|
|
||||||
Alternatively, one can use [`cargo
|
|
||||||
binstall`](https://github.com/cargo-bins/cargo-binstall) to install a ripgrep
|
|
||||||
binary directly from GitHub:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ cargo binstall ripgrep
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
### Building
|
|
||||||
|
|
||||||
ripgrep is written in Rust, so you'll need to grab a
|
|
||||||
[Rust installation](https://www.rust-lang.org/) in order to compile it.
|
|
||||||
ripgrep compiles with Rust 1.85.0 (stable) or newer. In general, ripgrep tracks
|
|
||||||
the latest stable release of the Rust compiler.
|
|
||||||
|
|
||||||
To build ripgrep:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ git clone https://github.com/BurntSushi/ripgrep
|
|
||||||
$ cd ripgrep
|
|
||||||
$ cargo build --release
|
|
||||||
$ ./target/release/rg --version
|
|
||||||
0.1.3
|
|
||||||
```
|
|
||||||
|
|
||||||
**NOTE:** In the past, ripgrep supported a `simd-accel` Cargo feature when
|
|
||||||
using a Rust nightly compiler. This only benefited UTF-16 transcoding.
|
|
||||||
Since it required unstable features, this build mode was prone to breakage.
|
|
||||||
Because of that, support for it has been removed. If you want SIMD
|
|
||||||
optimizations for UTF-16 transcoding, then you'll have to petition the
|
|
||||||
[`encoding_rs`](https://github.com/hsivonen/encoding_rs) project to use stable
|
|
||||||
APIs.
|
|
||||||
|
|
||||||
Finally, optional PCRE2 support can be built with ripgrep by enabling the
|
|
||||||
`pcre2` feature:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ cargo build --release --features 'pcre2'
|
|
||||||
```
|
|
||||||
|
|
||||||
Enabling the PCRE2 feature works with a stable Rust compiler and will
|
|
||||||
attempt to automatically find and link with your system's PCRE2 library via
|
|
||||||
`pkg-config`. If one doesn't exist, then ripgrep will build PCRE2 from source
|
|
||||||
using your system's C compiler and then statically link it into the final
|
|
||||||
executable. Static linking can be forced even when there is an available PCRE2
|
|
||||||
system library by either building ripgrep with the MUSL target or by setting
|
|
||||||
`PCRE2_SYS_STATIC=1`.
|
|
||||||
|
|
||||||
ripgrep can be built with the MUSL target on Linux by first installing the MUSL
|
|
||||||
library on your system (consult your friendly neighborhood package manager).
|
|
||||||
Then you just need to add MUSL support to your Rust toolchain and rebuild
|
|
||||||
ripgrep, which yields a fully static executable:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ rustup target add x86_64-unknown-linux-musl
|
|
||||||
$ cargo build --release --target x86_64-unknown-linux-musl
|
|
||||||
```
|
|
||||||
|
|
||||||
Applying the `--features` flag from above works as expected. If you want to
|
|
||||||
build a static executable with MUSL and with PCRE2, then you will need to have
|
|
||||||
`musl-gcc` installed, which might be in a separate package from the actual
|
|
||||||
MUSL library, depending on your Linux distribution.
|
|
||||||
|
|
||||||
|
|
||||||
### Running tests
|
|
||||||
|
|
||||||
ripgrep is relatively well-tested, including both unit tests and integration
|
|
||||||
tests. To run the full test suite, use:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ cargo test --all
|
|
||||||
```
|
|
||||||
|
|
||||||
from the repository root.
|
|
||||||
|
|
||||||
|
|
||||||
### Related tools
|
|
||||||
|
|
||||||
* [delta](https://github.com/dandavison/delta) is a syntax highlighting
|
|
||||||
pager that supports the `rg --json` output format. So all you need to do to
|
|
||||||
make it work is `rg --json pattern | delta`. See [delta's manual section on
|
|
||||||
grep](https://dandavison.github.io/delta/grep.html) for more details.
|
|
||||||
|
|
||||||
|
|
||||||
### Vulnerability reporting
|
|
||||||
|
|
||||||
For reporting a security vulnerability, please
|
|
||||||
[contact Andrew Gallant](https://blog.burntsushi.net/about/).
|
|
||||||
The contact page has my email address and PGP public key if you wish to send an
|
|
||||||
encrypted message.
|
|
||||||
|
|
||||||
|
|
||||||
### Translations
|
|
||||||
|
|
||||||
The following is a list of known translations of ripgrep's documentation. These
|
|
||||||
are unofficially maintained and may not be up to date.
|
|
||||||
|
|
||||||
* [Chinese](https://github.com/chinanf-boy/ripgrep-zh#%E6%9B%B4%E6%96%B0-)
|
|
||||||
* [Spanish](https://github.com/UltiRequiem/traducciones/tree/master/ripgrep)
|
|
||||||
|
|||||||
6
build.rs
6
build.rs
@@ -24,11 +24,11 @@ fn set_windows_exe_options() {
|
|||||||
|
|
||||||
println!("cargo:rerun-if-changed={MANIFEST}");
|
println!("cargo:rerun-if-changed={MANIFEST}");
|
||||||
// Embed the Windows application manifest file.
|
// Embed the Windows application manifest file.
|
||||||
println!("cargo:rustc-link-arg-bin=rg=/MANIFEST:EMBED");
|
println!("cargo:rustc-link-arg-bin=rgs=/MANIFEST:EMBED");
|
||||||
println!("cargo:rustc-link-arg-bin=rg=/MANIFESTINPUT:{manifest}");
|
println!("cargo:rustc-link-arg-bin=rgs=/MANIFESTINPUT:{manifest}");
|
||||||
// Turn linker warnings into errors. Helps debugging, otherwise the
|
// Turn linker warnings into errors. Helps debugging, otherwise the
|
||||||
// warnings get squashed (I believe).
|
// warnings get squashed (I believe).
|
||||||
println!("cargo:rustc-link-arg-bin=rg=/WX");
|
println!("cargo:rustc-link-arg-bin=rgs=/WX");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Make the current git hash available to the build as the environment
|
/// Make the current git hash available to the build as the environment
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ version="$1"
|
|||||||
# Linux and Darwin builds.
|
# Linux and Darwin builds.
|
||||||
for arch in i686 x86_64; do
|
for arch in i686 x86_64; do
|
||||||
for target in apple-darwin unknown-linux-musl; do
|
for target in apple-darwin unknown-linux-musl; do
|
||||||
url="https://github.com/BurntSushi/ripgrep/releases/download/$version/ripgrep-$version-$arch-$target.tar.gz"
|
url="https://git.peisongxiao.com/peisongxiao/rgs/releases/download/$version/rgs-$version-$arch-$target.tar.gz"
|
||||||
sha=$(curl -sfSL "$url" | sha256sum)
|
sha=$(curl -sfSL "$url" | sha256sum)
|
||||||
echo "$version-$arch-$target $sha"
|
echo "$version-$arch-$target $sha"
|
||||||
done
|
done
|
||||||
@@ -19,7 +19,7 @@ done
|
|||||||
|
|
||||||
# Source.
|
# Source.
|
||||||
for ext in zip tar.gz; do
|
for ext in zip tar.gz; do
|
||||||
url="https://github.com/BurntSushi/ripgrep/archive/$version.$ext"
|
url="https://git.peisongxiao.com/peisongxiao/rgs/archive/$version.$ext"
|
||||||
sha=$(curl -sfSL "$url" | sha256sum)
|
sha=$(curl -sfSL "$url" | sha256sum)
|
||||||
echo "source.$ext $sha"
|
echo "source.$ext $sha"
|
||||||
done
|
done
|
||||||
|
|||||||
@@ -18,11 +18,11 @@ get_comp_args() {
|
|||||||
|
|
||||||
main() {
|
main() {
|
||||||
local diff
|
local diff
|
||||||
local rg="${0:a:h}/../${TARGET_DIR:-target}/release/rg"
|
local rg="${0:a:h}/../${TARGET_DIR:-target}/release/rgs"
|
||||||
local _rg="${0:a:h}/../crates/core/flags/complete/rg.zsh"
|
local _rg="${0:a:h}/../crates/core/flags/complete/rgs.zsh"
|
||||||
local -a help_args comp_args
|
local -a help_args comp_args
|
||||||
|
|
||||||
[[ -e $rg ]] || rg=${rg/%\/release\/rg/\/debug\/rg}
|
[[ -e $rg ]] || rg=${rg/%\/release\/rgs/\/debug\/rgs}
|
||||||
|
|
||||||
rg=${rg:a}
|
rg=${rg:a}
|
||||||
_rg=${_rg:a}
|
_rg=${_rg:a}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#compdef rg
|
#compdef rgs
|
||||||
|
|
||||||
##
|
##
|
||||||
# zsh completion function for ripgrep
|
# zsh completion function for rgs
|
||||||
#
|
#
|
||||||
# Run ci/test-complete after building to ensure that the options supported by
|
# Run ci/test-complete after building to ensure that the options supported by
|
||||||
# this function stay in synch with the `rg` binary.
|
# this function stay in synch with the `rg` binary.
|
||||||
@@ -96,6 +96,8 @@ _rg() {
|
|||||||
+ '(file-name)' # File-name options
|
+ '(file-name)' # File-name options
|
||||||
{-H,--with-filename}'[show file name for matches]'
|
{-H,--with-filename}'[show file name for matches]'
|
||||||
{-I,--no-filename}"[don't show file name for matches]"
|
{-I,--no-filename}"[don't show file name for matches]"
|
||||||
|
'--in-file-index[show per-file match index in output]'
|
||||||
|
'--no-in-file-index[hide per-file match index in output]'
|
||||||
|
|
||||||
+ '(file-system)' # File system options
|
+ '(file-system)' # File system options
|
||||||
"--one-file-system[don't descend into directories on other file systems]"
|
"--one-file-system[don't descend into directories on other file systems]"
|
||||||
@@ -210,6 +212,7 @@ _rg() {
|
|||||||
|
|
||||||
+ '(multiline)' # Multiline options
|
+ '(multiline)' # Multiline options
|
||||||
{-U,--multiline}'[permit matching across multiple lines]'
|
{-U,--multiline}'[permit matching across multiple lines]'
|
||||||
|
{-W+,--multiline-window=}'[limit multiline matches to NUM lines (with -U enabled implicitly)]:number of lines'
|
||||||
$no'(multiline-dotall)--no-multiline[restrict matches to at most one line each]'
|
$no'(multiline-dotall)--no-multiline[restrict matches to at most one line each]'
|
||||||
|
|
||||||
+ '(multiline-dotall)' # Multiline DOTALL options
|
+ '(multiline-dotall)' # Multiline DOTALL options
|
||||||
@@ -279,6 +282,10 @@ _rg() {
|
|||||||
+ '(threads)' # Thread-count options
|
+ '(threads)' # Thread-count options
|
||||||
'(sort)'{-j+,--threads=}'[specify approximate number of threads to use]:number of threads'
|
'(sort)'{-j+,--threads=}'[specify approximate number of threads to use]:number of threads'
|
||||||
|
|
||||||
|
+ '(squash)' # Squash options
|
||||||
|
'--squash[squash contiguous whitespace into a single space]'
|
||||||
|
'--squash-nl-only[squash new lines into a single space]'
|
||||||
|
|
||||||
+ '(trim)' # Trim options
|
+ '(trim)' # Trim options
|
||||||
'--trim[trim any ASCII whitespace prefix from each line]'
|
'--trim[trim any ASCII whitespace prefix from each line]'
|
||||||
$no"--no-trim[don't trim ASCII whitespace prefix from each line]"
|
$no"--no-trim[don't trim ASCII whitespace prefix from each line]"
|
||||||
@@ -26,7 +26,7 @@ pub(crate) fn generate() -> String {
|
|||||||
})
|
})
|
||||||
.collect::<Vec<String>>()
|
.collect::<Vec<String>>()
|
||||||
.join("\n");
|
.join("\n");
|
||||||
include_str!("rg.zsh")
|
include_str!("rgs.zsh")
|
||||||
.replace("!ENCODINGS!", super::ENCODINGS.trim_end())
|
.replace("!ENCODINGS!", super::ENCODINGS.trim_end())
|
||||||
.replace("!HYPERLINK_ALIASES!", &hyperlink_alias_descriptions)
|
.replace("!HYPERLINK_ALIASES!", &hyperlink_alias_descriptions)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,6 +97,7 @@ pub(super) const FLAGS: &[&dyn Flag] = &[
|
|||||||
&MaxFilesize,
|
&MaxFilesize,
|
||||||
&Mmap,
|
&Mmap,
|
||||||
&Multiline,
|
&Multiline,
|
||||||
|
&MultilineWindow,
|
||||||
&MultilineDotall,
|
&MultilineDotall,
|
||||||
&NoConfig,
|
&NoConfig,
|
||||||
&NoIgnore,
|
&NoIgnore,
|
||||||
@@ -133,6 +134,8 @@ pub(super) const FLAGS: &[&dyn Flag] = &[
|
|||||||
&Text,
|
&Text,
|
||||||
&Threads,
|
&Threads,
|
||||||
&Trace,
|
&Trace,
|
||||||
|
&Squash,
|
||||||
|
&SquashNlOnly,
|
||||||
&Trim,
|
&Trim,
|
||||||
&Type,
|
&Type,
|
||||||
&TypeNot,
|
&TypeNot,
|
||||||
@@ -142,6 +145,7 @@ pub(super) const FLAGS: &[&dyn Flag] = &[
|
|||||||
&Unrestricted,
|
&Unrestricted,
|
||||||
&Version,
|
&Version,
|
||||||
&Vimgrep,
|
&Vimgrep,
|
||||||
|
&InFileIndex,
|
||||||
&WithFilename,
|
&WithFilename,
|
||||||
&WithFilenameNo,
|
&WithFilenameNo,
|
||||||
&WordRegexp,
|
&WordRegexp,
|
||||||
@@ -4185,7 +4189,14 @@ This overrides the \flag{stop-on-nonmatch} flag.
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn update(&self, v: FlagValue, args: &mut LowArgs) -> anyhow::Result<()> {
|
fn update(&self, v: FlagValue, args: &mut LowArgs) -> anyhow::Result<()> {
|
||||||
args.multiline = v.unwrap_switch();
|
let enabled = v.unwrap_switch();
|
||||||
|
if !enabled && args.multiline_window.is_some() {
|
||||||
|
anyhow::bail!(
|
||||||
|
"--no-multiline cannot be used with --multiline-window \
|
||||||
|
(which implicitly enables --multiline)"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
args.multiline = enabled;
|
||||||
if args.multiline {
|
if args.multiline {
|
||||||
args.stop_on_nonmatch = false;
|
args.stop_on_nonmatch = false;
|
||||||
}
|
}
|
||||||
@@ -4209,6 +4220,68 @@ fn test_multiline() {
|
|||||||
assert_eq!(false, args.multiline);
|
assert_eq!(false, args.multiline);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// --multiline-window
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct MultilineWindow;
|
||||||
|
|
||||||
|
impl Flag for MultilineWindow {
|
||||||
|
fn is_switch(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
fn name_short(&self) -> Option<u8> {
|
||||||
|
Some(b'W')
|
||||||
|
}
|
||||||
|
fn name_long(&self) -> &'static str {
|
||||||
|
"multiline-window"
|
||||||
|
}
|
||||||
|
fn doc_variable(&self) -> Option<&'static str> {
|
||||||
|
Some("NUM")
|
||||||
|
}
|
||||||
|
fn doc_category(&self) -> Category {
|
||||||
|
Category::Search
|
||||||
|
}
|
||||||
|
fn doc_short(&self) -> &'static str {
|
||||||
|
r"Limit multiline matches to a fixed number of lines."
|
||||||
|
}
|
||||||
|
fn doc_long(&self) -> &'static str {
|
||||||
|
r#"
|
||||||
|
Limit the maximum number of lines that a multiline match may span to
|
||||||
|
\fINUM\fP (use \fB--multiline-window=\fP\fINUM\fP).
|
||||||
|
.sp
|
||||||
|
This flag implicitly enables \flag{multiline}. Matches are found as if the file being
|
||||||
|
searched were limited to \fINUM\fP lines at a time, which can prevent
|
||||||
|
unintended long matches while still enabling multi-line searching.
|
||||||
|
.sp
|
||||||
|
The value of \fINUM\fP must be at least 1.
|
||||||
|
"#
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&self, v: FlagValue, args: &mut LowArgs) -> anyhow::Result<()> {
|
||||||
|
let lines = convert::usize(&v.unwrap_value())?;
|
||||||
|
if lines == 0 {
|
||||||
|
anyhow::bail!("--multiline-window must be at least 1");
|
||||||
|
}
|
||||||
|
args.multiline_window = Some(lines);
|
||||||
|
args.multiline = true;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[test]
|
||||||
|
fn test_multiline_window() {
|
||||||
|
let args = parse_low_raw(None::<&str>).unwrap();
|
||||||
|
assert_eq!(None, args.multiline_window);
|
||||||
|
|
||||||
|
let args = parse_low_raw(["--multiline-window=2"]).unwrap();
|
||||||
|
assert_eq!(Some(2), args.multiline_window);
|
||||||
|
assert_eq!(true, args.multiline);
|
||||||
|
|
||||||
|
let args = parse_low_raw(["-W", "3"]).unwrap();
|
||||||
|
assert_eq!(Some(3), args.multiline_window);
|
||||||
|
assert_eq!(true, args.multiline);
|
||||||
|
}
|
||||||
|
|
||||||
/// --multiline-dotall
|
/// --multiline-dotall
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct MultilineDotall;
|
struct MultilineDotall;
|
||||||
@@ -6811,6 +6884,88 @@ fn test_trace() {
|
|||||||
assert_eq!(Some(LoggingMode::Trace), args.logging);
|
assert_eq!(Some(LoggingMode::Trace), args.logging);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// --squash
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Squash;
|
||||||
|
|
||||||
|
impl Flag for Squash {
|
||||||
|
fn is_switch(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
fn name_long(&self) -> &'static str {
|
||||||
|
"squash"
|
||||||
|
}
|
||||||
|
fn doc_category(&self) -> Category {
|
||||||
|
Category::Output
|
||||||
|
}
|
||||||
|
fn doc_short(&self) -> &'static str {
|
||||||
|
r"Squash contiguous whitespace in output to a single space."
|
||||||
|
}
|
||||||
|
fn doc_long(&self) -> &'static str {
|
||||||
|
r#"
|
||||||
|
Squash any contiguous Unicode whitespace (including new lines) into a single
|
||||||
|
ASCII space when printing matches.
|
||||||
|
"#
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&self, v: FlagValue, args: &mut LowArgs) -> anyhow::Result<()> {
|
||||||
|
assert!(v.unwrap_switch(), "--squash can only be enabled");
|
||||||
|
args.squash = grep::printer::SquashMode::Whitespace;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[test]
|
||||||
|
fn test_squash() {
|
||||||
|
let args = parse_low_raw(None::<&str>).unwrap();
|
||||||
|
assert_eq!(grep::printer::SquashMode::None, args.squash);
|
||||||
|
|
||||||
|
let args = parse_low_raw(["--squash"]).unwrap();
|
||||||
|
assert_eq!(grep::printer::SquashMode::Whitespace, args.squash);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// --squash-nl-only
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct SquashNlOnly;
|
||||||
|
|
||||||
|
impl Flag for SquashNlOnly {
|
||||||
|
fn is_switch(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
fn name_long(&self) -> &'static str {
|
||||||
|
"squash-nl-only"
|
||||||
|
}
|
||||||
|
fn doc_category(&self) -> Category {
|
||||||
|
Category::Output
|
||||||
|
}
|
||||||
|
fn doc_short(&self) -> &'static str {
|
||||||
|
r"Squash new lines into spaces in output."
|
||||||
|
}
|
||||||
|
fn doc_long(&self) -> &'static str {
|
||||||
|
r#"
|
||||||
|
Squash contiguous line terminators into a single ASCII space when printing
|
||||||
|
matches. Other whitespace is preserved.
|
||||||
|
"#
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&self, v: FlagValue, args: &mut LowArgs) -> anyhow::Result<()> {
|
||||||
|
assert!(v.unwrap_switch(), "--squash-nl-only can only be enabled");
|
||||||
|
args.squash = grep::printer::SquashMode::Newlines;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[test]
|
||||||
|
fn test_squash_nl_only() {
|
||||||
|
let args = parse_low_raw(None::<&str>).unwrap();
|
||||||
|
assert_eq!(grep::printer::SquashMode::None, args.squash);
|
||||||
|
|
||||||
|
let args = parse_low_raw(["--squash-nl-only"]).unwrap();
|
||||||
|
assert_eq!(grep::printer::SquashMode::Newlines, args.squash);
|
||||||
|
}
|
||||||
|
|
||||||
/// --trim
|
/// --trim
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Trim;
|
struct Trim;
|
||||||
@@ -7401,6 +7556,53 @@ fn test_vimgrep() {
|
|||||||
assert_eq!(true, args.vimgrep);
|
assert_eq!(true, args.vimgrep);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// --in-file-index
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct InFileIndex;
|
||||||
|
|
||||||
|
impl Flag for InFileIndex {
|
||||||
|
fn is_switch(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
fn name_long(&self) -> &'static str {
|
||||||
|
"in-file-index"
|
||||||
|
}
|
||||||
|
fn name_negated(&self) -> Option<&'static str> {
|
||||||
|
Some("no-in-file-index")
|
||||||
|
}
|
||||||
|
fn doc_category(&self) -> Category {
|
||||||
|
Category::Output
|
||||||
|
}
|
||||||
|
fn doc_short(&self) -> &'static str {
|
||||||
|
r"Prefix matches with an index per file."
|
||||||
|
}
|
||||||
|
fn doc_long(&self) -> &'static str {
|
||||||
|
r"
|
||||||
|
When enabled, ripgrep prefixes each matching line with an index that is
|
||||||
|
incremented per file. The format is \fIFILE\fP[\fIN\fP]:\fILINE\fP:, which can
|
||||||
|
disambiguate multi-line matches that print the same line multiple times.
|
||||||
|
"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&self, v: FlagValue, args: &mut LowArgs) -> anyhow::Result<()> {
|
||||||
|
args.in_file_index = v.unwrap_switch();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[test]
|
||||||
|
fn test_in_file_index() {
|
||||||
|
let args = parse_low_raw(None::<&str>).unwrap();
|
||||||
|
assert_eq!(false, args.in_file_index);
|
||||||
|
|
||||||
|
let args = parse_low_raw(["--in-file-index"]).unwrap();
|
||||||
|
assert_eq!(true, args.in_file_index);
|
||||||
|
|
||||||
|
let args = parse_low_raw(["--in-file-index", "--no-in-file-index"]).unwrap();
|
||||||
|
assert_eq!(false, args.in_file_index);
|
||||||
|
}
|
||||||
|
|
||||||
/// --with-filename
|
/// --with-filename
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct WithFilename;
|
struct WithFilename;
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use std::{
|
|||||||
|
|
||||||
use {
|
use {
|
||||||
bstr::BString,
|
bstr::BString,
|
||||||
grep::printer::{ColorSpecs, SummaryKind},
|
grep::printer::{ColorSpecs, SquashMode, SummaryKind},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -61,6 +61,7 @@ pub(crate) struct HiArgs {
|
|||||||
ignore_file_case_insensitive: bool,
|
ignore_file_case_insensitive: bool,
|
||||||
ignore_file: Vec<PathBuf>,
|
ignore_file: Vec<PathBuf>,
|
||||||
include_zero: bool,
|
include_zero: bool,
|
||||||
|
in_file_index: bool,
|
||||||
invert_match: bool,
|
invert_match: bool,
|
||||||
is_terminal_stdout: bool,
|
is_terminal_stdout: bool,
|
||||||
line_number: bool,
|
line_number: bool,
|
||||||
@@ -73,6 +74,7 @@ pub(crate) struct HiArgs {
|
|||||||
mode: Mode,
|
mode: Mode,
|
||||||
multiline: bool,
|
multiline: bool,
|
||||||
multiline_dotall: bool,
|
multiline_dotall: bool,
|
||||||
|
multiline_window: Option<usize>,
|
||||||
no_ignore_dot: bool,
|
no_ignore_dot: bool,
|
||||||
no_ignore_exclude: bool,
|
no_ignore_exclude: bool,
|
||||||
no_ignore_files: bool,
|
no_ignore_files: bool,
|
||||||
@@ -98,6 +100,7 @@ pub(crate) struct HiArgs {
|
|||||||
sort: Option<SortMode>,
|
sort: Option<SortMode>,
|
||||||
stats: Option<grep::printer::Stats>,
|
stats: Option<grep::printer::Stats>,
|
||||||
stop_on_nonmatch: bool,
|
stop_on_nonmatch: bool,
|
||||||
|
squash: SquashMode,
|
||||||
threads: usize,
|
threads: usize,
|
||||||
trim: bool,
|
trim: bool,
|
||||||
types: ignore::types::Types,
|
types: ignore::types::Types,
|
||||||
@@ -278,6 +281,7 @@ impl HiArgs {
|
|||||||
ignore_file: low.ignore_file,
|
ignore_file: low.ignore_file,
|
||||||
ignore_file_case_insensitive: low.ignore_file_case_insensitive,
|
ignore_file_case_insensitive: low.ignore_file_case_insensitive,
|
||||||
include_zero: low.include_zero,
|
include_zero: low.include_zero,
|
||||||
|
in_file_index: low.in_file_index,
|
||||||
invert_match: low.invert_match,
|
invert_match: low.invert_match,
|
||||||
is_terminal_stdout: state.is_terminal_stdout,
|
is_terminal_stdout: state.is_terminal_stdout,
|
||||||
line_number,
|
line_number,
|
||||||
@@ -289,6 +293,7 @@ impl HiArgs {
|
|||||||
mmap_choice,
|
mmap_choice,
|
||||||
multiline: low.multiline,
|
multiline: low.multiline,
|
||||||
multiline_dotall: low.multiline_dotall,
|
multiline_dotall: low.multiline_dotall,
|
||||||
|
multiline_window: low.multiline_window,
|
||||||
no_ignore_dot: low.no_ignore_dot,
|
no_ignore_dot: low.no_ignore_dot,
|
||||||
no_ignore_exclude: low.no_ignore_exclude,
|
no_ignore_exclude: low.no_ignore_exclude,
|
||||||
no_ignore_files: low.no_ignore_files,
|
no_ignore_files: low.no_ignore_files,
|
||||||
@@ -313,6 +318,7 @@ impl HiArgs {
|
|||||||
sort: low.sort,
|
sort: low.sort,
|
||||||
stats,
|
stats,
|
||||||
stop_on_nonmatch: low.stop_on_nonmatch,
|
stop_on_nonmatch: low.stop_on_nonmatch,
|
||||||
|
squash: low.squash,
|
||||||
threads,
|
threads,
|
||||||
trim: low.trim,
|
trim: low.trim,
|
||||||
types,
|
types,
|
||||||
@@ -616,6 +622,7 @@ impl HiArgs {
|
|||||||
.column(self.column)
|
.column(self.column)
|
||||||
.heading(self.heading)
|
.heading(self.heading)
|
||||||
.hyperlink(self.hyperlink_config.clone())
|
.hyperlink(self.hyperlink_config.clone())
|
||||||
|
.in_file_index(self.in_file_index)
|
||||||
.max_columns_preview(self.max_columns_preview)
|
.max_columns_preview(self.max_columns_preview)
|
||||||
.max_columns(self.max_columns)
|
.max_columns(self.max_columns)
|
||||||
.only_matching(self.only_matching)
|
.only_matching(self.only_matching)
|
||||||
@@ -624,6 +631,7 @@ impl HiArgs {
|
|||||||
.per_match_one_line(true)
|
.per_match_one_line(true)
|
||||||
.per_match(self.vimgrep)
|
.per_match(self.vimgrep)
|
||||||
.replacement(self.replace.clone().map(|r| r.into()))
|
.replacement(self.replace.clone().map(|r| r.into()))
|
||||||
|
.squash(self.squash)
|
||||||
.separator_context(self.context_separator.clone().into_bytes())
|
.separator_context(self.context_separator.clone().into_bytes())
|
||||||
.separator_field_context(
|
.separator_field_context(
|
||||||
self.field_context_separator.clone().into_bytes(),
|
self.field_context_separator.clone().into_bytes(),
|
||||||
@@ -723,6 +731,7 @@ impl HiArgs {
|
|||||||
.invert_match(self.invert_match)
|
.invert_match(self.invert_match)
|
||||||
.line_number(self.line_number)
|
.line_number(self.line_number)
|
||||||
.multi_line(self.multiline)
|
.multi_line(self.multiline)
|
||||||
|
.multiline_window(self.multiline_window)
|
||||||
.memory_map(self.mmap_choice.clone())
|
.memory_map(self.mmap_choice.clone())
|
||||||
.stop_on_nonmatch(self.stop_on_nonmatch);
|
.stop_on_nonmatch(self.stop_on_nonmatch);
|
||||||
match self.context {
|
match self.context {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use std::{
|
|||||||
|
|
||||||
use {
|
use {
|
||||||
bstr::{BString, ByteVec},
|
bstr::{BString, ByteVec},
|
||||||
grep::printer::{HyperlinkFormat, UserColorSpec},
|
grep::printer::{HyperlinkFormat, SquashMode, UserColorSpec},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A collection of "low level" arguments.
|
/// A collection of "low level" arguments.
|
||||||
@@ -65,6 +65,7 @@ pub(crate) struct LowArgs {
|
|||||||
pub(crate) ignore_file: Vec<PathBuf>,
|
pub(crate) ignore_file: Vec<PathBuf>,
|
||||||
pub(crate) ignore_file_case_insensitive: bool,
|
pub(crate) ignore_file_case_insensitive: bool,
|
||||||
pub(crate) include_zero: bool,
|
pub(crate) include_zero: bool,
|
||||||
|
pub(crate) in_file_index: bool,
|
||||||
pub(crate) invert_match: bool,
|
pub(crate) invert_match: bool,
|
||||||
pub(crate) line_number: Option<bool>,
|
pub(crate) line_number: Option<bool>,
|
||||||
pub(crate) logging: Option<LoggingMode>,
|
pub(crate) logging: Option<LoggingMode>,
|
||||||
@@ -76,6 +77,7 @@ pub(crate) struct LowArgs {
|
|||||||
pub(crate) mmap: MmapMode,
|
pub(crate) mmap: MmapMode,
|
||||||
pub(crate) multiline: bool,
|
pub(crate) multiline: bool,
|
||||||
pub(crate) multiline_dotall: bool,
|
pub(crate) multiline_dotall: bool,
|
||||||
|
pub(crate) multiline_window: Option<usize>,
|
||||||
pub(crate) no_config: bool,
|
pub(crate) no_config: bool,
|
||||||
pub(crate) no_ignore_dot: bool,
|
pub(crate) no_ignore_dot: bool,
|
||||||
pub(crate) no_ignore_exclude: bool,
|
pub(crate) no_ignore_exclude: bool,
|
||||||
@@ -101,6 +103,7 @@ pub(crate) struct LowArgs {
|
|||||||
pub(crate) sort: Option<SortMode>,
|
pub(crate) sort: Option<SortMode>,
|
||||||
pub(crate) stats: bool,
|
pub(crate) stats: bool,
|
||||||
pub(crate) stop_on_nonmatch: bool,
|
pub(crate) stop_on_nonmatch: bool,
|
||||||
|
pub(crate) squash: SquashMode,
|
||||||
pub(crate) threads: Option<usize>,
|
pub(crate) threads: Option<usize>,
|
||||||
pub(crate) trim: bool,
|
pub(crate) trim: bool,
|
||||||
pub(crate) type_changes: Vec<TypeChange>,
|
pub(crate) type_changes: Vec<TypeChange>,
|
||||||
@@ -229,13 +232,14 @@ pub(crate) enum GenerateMode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Indicates how ripgrep should treat binary data.
|
/// Indicates how ripgrep should treat binary data.
|
||||||
#[derive(Debug, Eq, PartialEq)]
|
#[derive(Debug, Default, Eq, PartialEq)]
|
||||||
pub(crate) enum BinaryMode {
|
pub(crate) enum BinaryMode {
|
||||||
/// Automatically determine the binary mode to use. Essentially, when
|
/// Automatically determine the binary mode to use. Essentially, when
|
||||||
/// a file is searched explicitly, then it will be searched using the
|
/// a file is searched explicitly, then it will be searched using the
|
||||||
/// `SearchAndSuppress` strategy. Otherwise, it will be searched in a way
|
/// `SearchAndSuppress` strategy. Otherwise, it will be searched in a way
|
||||||
/// that attempts to skip binary files as much as possible. That is, once
|
/// that attempts to skip binary files as much as possible. That is, once
|
||||||
/// a file is classified as binary, searching will immediately stop.
|
/// a file is classified as binary, searching will immediately stop.
|
||||||
|
#[default]
|
||||||
Auto,
|
Auto,
|
||||||
/// Search files even when they have binary data, but if a match is found,
|
/// Search files even when they have binary data, but if a match is found,
|
||||||
/// suppress it and emit a warning.
|
/// suppress it and emit a warning.
|
||||||
@@ -251,12 +255,6 @@ pub(crate) enum BinaryMode {
|
|||||||
AsText,
|
AsText,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for BinaryMode {
|
|
||||||
fn default() -> BinaryMode {
|
|
||||||
BinaryMode::Auto
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Indicates what kind of boundary mode to use (line or word).
|
/// Indicates what kind of boundary mode to use (line or word).
|
||||||
#[derive(Debug, Eq, PartialEq)]
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
pub(crate) enum BoundaryMode {
|
pub(crate) enum BoundaryMode {
|
||||||
@@ -269,10 +267,11 @@ pub(crate) enum BoundaryMode {
|
|||||||
/// Indicates the buffer mode that ripgrep should use when printing output.
|
/// Indicates the buffer mode that ripgrep should use when printing output.
|
||||||
///
|
///
|
||||||
/// The default is `Auto`.
|
/// The default is `Auto`.
|
||||||
#[derive(Debug, Eq, PartialEq)]
|
#[derive(Debug, Default, Eq, PartialEq)]
|
||||||
pub(crate) enum BufferMode {
|
pub(crate) enum BufferMode {
|
||||||
/// Select the buffer mode, 'line' or 'block', automatically based on
|
/// Select the buffer mode, 'line' or 'block', automatically based on
|
||||||
/// whether stdout is connected to a tty.
|
/// whether stdout is connected to a tty.
|
||||||
|
#[default]
|
||||||
Auto,
|
Auto,
|
||||||
/// Flush the output buffer whenever a line terminator is seen.
|
/// Flush the output buffer whenever a line terminator is seen.
|
||||||
///
|
///
|
||||||
@@ -287,18 +286,13 @@ pub(crate) enum BufferMode {
|
|||||||
Block,
|
Block,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for BufferMode {
|
|
||||||
fn default() -> BufferMode {
|
|
||||||
BufferMode::Auto
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Indicates the case mode for how to interpret all patterns given to ripgrep.
|
/// Indicates the case mode for how to interpret all patterns given to ripgrep.
|
||||||
///
|
///
|
||||||
/// The default is `Sensitive`.
|
/// The default is `Sensitive`.
|
||||||
#[derive(Debug, Eq, PartialEq)]
|
#[derive(Debug, Default, Eq, PartialEq)]
|
||||||
pub(crate) enum CaseMode {
|
pub(crate) enum CaseMode {
|
||||||
/// Patterns are matched case sensitively. i.e., `a` does not match `A`.
|
/// Patterns are matched case sensitively. i.e., `a` does not match `A`.
|
||||||
|
#[default]
|
||||||
Sensitive,
|
Sensitive,
|
||||||
/// Patterns are matched case insensitively. i.e., `a` does match `A`.
|
/// Patterns are matched case insensitively. i.e., `a` does match `A`.
|
||||||
Insensitive,
|
Insensitive,
|
||||||
@@ -308,21 +302,16 @@ pub(crate) enum CaseMode {
|
|||||||
Smart,
|
Smart,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for CaseMode {
|
|
||||||
fn default() -> CaseMode {
|
|
||||||
CaseMode::Sensitive
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Indicates whether ripgrep should include color/hyperlinks in its output.
|
/// Indicates whether ripgrep should include color/hyperlinks in its output.
|
||||||
///
|
///
|
||||||
/// The default is `Auto`.
|
/// The default is `Auto`.
|
||||||
#[derive(Debug, Eq, PartialEq)]
|
#[derive(Debug, Default, Eq, PartialEq)]
|
||||||
pub(crate) enum ColorChoice {
|
pub(crate) enum ColorChoice {
|
||||||
/// Color and hyperlinks will never be used.
|
/// Color and hyperlinks will never be used.
|
||||||
Never,
|
Never,
|
||||||
/// Color and hyperlinks will be used only when stdout is connected to a
|
/// Color and hyperlinks will be used only when stdout is connected to a
|
||||||
/// tty.
|
/// tty.
|
||||||
|
#[default]
|
||||||
Auto,
|
Auto,
|
||||||
/// Color will always be used.
|
/// Color will always be used.
|
||||||
Always,
|
Always,
|
||||||
@@ -335,12 +324,6 @@ pub(crate) enum ColorChoice {
|
|||||||
Ansi,
|
Ansi,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ColorChoice {
|
|
||||||
fn default() -> ColorChoice {
|
|
||||||
ColorChoice::Auto
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ColorChoice {
|
impl ColorChoice {
|
||||||
/// Convert this color choice to the corresponding termcolor type.
|
/// Convert this color choice to the corresponding termcolor type.
|
||||||
pub(crate) fn to_termcolor(&self) -> termcolor::ColorChoice {
|
pub(crate) fn to_termcolor(&self) -> termcolor::ColorChoice {
|
||||||
@@ -529,9 +512,10 @@ impl ContextSeparator {
|
|||||||
/// The encoding mode the searcher will use.
|
/// The encoding mode the searcher will use.
|
||||||
///
|
///
|
||||||
/// The default is `Auto`.
|
/// The default is `Auto`.
|
||||||
#[derive(Debug, Eq, PartialEq)]
|
#[derive(Debug, Default, Eq, PartialEq)]
|
||||||
pub(crate) enum EncodingMode {
|
pub(crate) enum EncodingMode {
|
||||||
/// Use only BOM sniffing to auto-detect an encoding.
|
/// Use only BOM sniffing to auto-detect an encoding.
|
||||||
|
#[default]
|
||||||
Auto,
|
Auto,
|
||||||
/// Use an explicit encoding forcefully, but let BOM sniffing override it.
|
/// Use an explicit encoding forcefully, but let BOM sniffing override it.
|
||||||
Some(grep::searcher::Encoding),
|
Some(grep::searcher::Encoding),
|
||||||
@@ -541,21 +525,16 @@ pub(crate) enum EncodingMode {
|
|||||||
Disabled,
|
Disabled,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for EncodingMode {
|
|
||||||
fn default() -> EncodingMode {
|
|
||||||
EncodingMode::Auto
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The regex engine to use.
|
/// The regex engine to use.
|
||||||
///
|
///
|
||||||
/// The default is `Default`.
|
/// The default is `Default`.
|
||||||
#[derive(Debug, Eq, PartialEq)]
|
#[derive(Debug, Default, Eq, PartialEq)]
|
||||||
pub(crate) enum EngineChoice {
|
pub(crate) enum EngineChoice {
|
||||||
/// Uses the default regex engine: Rust's `regex` crate.
|
/// Uses the default regex engine: Rust's `regex` crate.
|
||||||
///
|
///
|
||||||
/// (Well, technically it uses `regex-automata`, but `regex-automata` is
|
/// (Well, technically it uses `regex-automata`, but `regex-automata` is
|
||||||
/// the implementation of the `regex` crate.)
|
/// the implementation of the `regex` crate.)
|
||||||
|
#[default]
|
||||||
Default,
|
Default,
|
||||||
/// Dynamically select the right engine to use.
|
/// Dynamically select the right engine to use.
|
||||||
///
|
///
|
||||||
@@ -566,12 +545,6 @@ pub(crate) enum EngineChoice {
|
|||||||
PCRE2,
|
PCRE2,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for EngineChoice {
|
|
||||||
fn default() -> EngineChoice {
|
|
||||||
EngineChoice::Default
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The field context separator to use to between metadata for each contextual
|
/// The field context separator to use to between metadata for each contextual
|
||||||
/// line.
|
/// line.
|
||||||
///
|
///
|
||||||
@@ -651,10 +624,11 @@ pub(crate) enum LoggingMode {
|
|||||||
/// Indicates when to use memory maps.
|
/// Indicates when to use memory maps.
|
||||||
///
|
///
|
||||||
/// The default is `Auto`.
|
/// The default is `Auto`.
|
||||||
#[derive(Debug, Eq, PartialEq)]
|
#[derive(Debug, Default, Eq, PartialEq)]
|
||||||
pub(crate) enum MmapMode {
|
pub(crate) enum MmapMode {
|
||||||
/// This instructs ripgrep to use heuristics for selecting when to and not
|
/// This instructs ripgrep to use heuristics for selecting when to and not
|
||||||
/// to use memory maps for searching.
|
/// to use memory maps for searching.
|
||||||
|
#[default]
|
||||||
Auto,
|
Auto,
|
||||||
/// This instructs ripgrep to always try memory maps when possible. (Memory
|
/// This instructs ripgrep to always try memory maps when possible. (Memory
|
||||||
/// maps are not possible to use in all circumstances, for example, for
|
/// maps are not possible to use in all circumstances, for example, for
|
||||||
@@ -666,12 +640,6 @@ pub(crate) enum MmapMode {
|
|||||||
Never,
|
Never,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for MmapMode {
|
|
||||||
fn default() -> MmapMode {
|
|
||||||
MmapMode::Auto
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents a source of patterns that ripgrep should search for.
|
/// Represents a source of patterns that ripgrep should search for.
|
||||||
///
|
///
|
||||||
/// The reason to unify these is so that we can retain the order of `-f/--flag`
|
/// The reason to unify these is so that we can retain the order of `-f/--flag`
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ pub use crate::{
|
|||||||
HyperlinkFormat, HyperlinkFormatError, hyperlink_aliases,
|
HyperlinkFormat, HyperlinkFormatError, hyperlink_aliases,
|
||||||
},
|
},
|
||||||
path::{PathPrinter, PathPrinterBuilder},
|
path::{PathPrinter, PathPrinterBuilder},
|
||||||
standard::{Standard, StandardBuilder, StandardSink},
|
standard::{SquashMode, Standard, StandardBuilder, StandardSink},
|
||||||
stats::Stats,
|
stats::Stats,
|
||||||
summary::{Summary, SummaryBuilder, SummaryKind, SummarySink},
|
summary::{Summary, SummaryBuilder, SummaryKind, SummarySink},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -27,6 +27,23 @@ use crate::{
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Controls how whitespace is squashed in the standard printer output.
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub enum SquashMode {
|
||||||
|
/// Do not squash whitespace in output.
|
||||||
|
None,
|
||||||
|
/// Squash any Unicode whitespace into a single ASCII space.
|
||||||
|
Whitespace,
|
||||||
|
/// Squash line terminators into a single ASCII space.
|
||||||
|
Newlines,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for SquashMode {
|
||||||
|
fn default() -> SquashMode {
|
||||||
|
SquashMode::None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The configuration for the standard printer.
|
/// The configuration for the standard printer.
|
||||||
///
|
///
|
||||||
/// This is manipulated by the StandardBuilder and then referenced by the
|
/// This is manipulated by the StandardBuilder and then referenced by the
|
||||||
@@ -39,6 +56,8 @@ struct Config {
|
|||||||
stats: bool,
|
stats: bool,
|
||||||
heading: bool,
|
heading: bool,
|
||||||
path: bool,
|
path: bool,
|
||||||
|
in_file_index: bool,
|
||||||
|
squash: SquashMode,
|
||||||
only_matching: bool,
|
only_matching: bool,
|
||||||
per_match: bool,
|
per_match: bool,
|
||||||
per_match_one_line: bool,
|
per_match_one_line: bool,
|
||||||
@@ -64,6 +83,8 @@ impl Default for Config {
|
|||||||
stats: false,
|
stats: false,
|
||||||
heading: false,
|
heading: false,
|
||||||
path: true,
|
path: true,
|
||||||
|
in_file_index: false,
|
||||||
|
squash: SquashMode::None,
|
||||||
only_matching: false,
|
only_matching: false,
|
||||||
per_match: false,
|
per_match: false,
|
||||||
per_match_one_line: false,
|
per_match_one_line: false,
|
||||||
@@ -231,6 +252,12 @@ impl StandardBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// When enabled, prefix matching lines with a per-file match index.
|
||||||
|
pub fn in_file_index(&mut self, yes: bool) -> &mut StandardBuilder {
|
||||||
|
self.config.in_file_index = yes;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Only print the specific matches instead of the entire line containing
|
/// Only print the specific matches instead of the entire line containing
|
||||||
/// each match. Each match is printed on its own line. When multi line
|
/// each match. Each match is printed on its own line. When multi line
|
||||||
/// search is enabled, then matches spanning multiple lines are printed
|
/// search is enabled, then matches spanning multiple lines are printed
|
||||||
@@ -358,6 +385,12 @@ impl StandardBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Configure whitespace squashing in standard output.
|
||||||
|
pub fn squash(&mut self, mode: SquashMode) -> &mut StandardBuilder {
|
||||||
|
self.config.squash = mode;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Set the separator used between sets of search results.
|
/// Set the separator used between sets of search results.
|
||||||
///
|
///
|
||||||
/// When this is set, then it will be printed on its own line immediately
|
/// When this is set, then it will be printed on its own line immediately
|
||||||
@@ -528,6 +561,7 @@ impl<W: WriteColor> Standard<W> {
|
|||||||
path: None,
|
path: None,
|
||||||
start_time: Instant::now(),
|
start_time: Instant::now(),
|
||||||
match_count: 0,
|
match_count: 0,
|
||||||
|
in_file_index: 0,
|
||||||
binary_byte_offset: None,
|
binary_byte_offset: None,
|
||||||
stats,
|
stats,
|
||||||
needs_match_granularity,
|
needs_match_granularity,
|
||||||
@@ -564,6 +598,7 @@ impl<W: WriteColor> Standard<W> {
|
|||||||
path: Some(ppath),
|
path: Some(ppath),
|
||||||
start_time: Instant::now(),
|
start_time: Instant::now(),
|
||||||
match_count: 0,
|
match_count: 0,
|
||||||
|
in_file_index: 0,
|
||||||
binary_byte_offset: None,
|
binary_byte_offset: None,
|
||||||
stats,
|
stats,
|
||||||
needs_match_granularity,
|
needs_match_granularity,
|
||||||
@@ -644,6 +679,7 @@ pub struct StandardSink<'p, 's, M: Matcher, W> {
|
|||||||
path: Option<PrinterPath<'p>>,
|
path: Option<PrinterPath<'p>>,
|
||||||
start_time: Instant,
|
start_time: Instant,
|
||||||
match_count: u64,
|
match_count: u64,
|
||||||
|
in_file_index: u64,
|
||||||
binary_byte_offset: Option<u64>,
|
binary_byte_offset: Option<u64>,
|
||||||
stats: Option<Stats>,
|
stats: Option<Stats>,
|
||||||
needs_match_granularity: bool,
|
needs_match_granularity: bool,
|
||||||
@@ -769,6 +805,7 @@ impl<'p, 's, M: Matcher, W: WriteColor> Sink for StandardSink<'p, 's, M, W> {
|
|||||||
mat: &SinkMatch<'_>,
|
mat: &SinkMatch<'_>,
|
||||||
) -> Result<bool, io::Error> {
|
) -> Result<bool, io::Error> {
|
||||||
self.match_count += 1;
|
self.match_count += 1;
|
||||||
|
self.in_file_index += 1;
|
||||||
|
|
||||||
self.record_matches(
|
self.record_matches(
|
||||||
searcher,
|
searcher,
|
||||||
@@ -842,6 +879,7 @@ impl<'p, 's, M: Matcher, W: WriteColor> Sink for StandardSink<'p, 's, M, W> {
|
|||||||
self.standard.wtr.borrow_mut().reset_count();
|
self.standard.wtr.borrow_mut().reset_count();
|
||||||
self.start_time = Instant::now();
|
self.start_time = Instant::now();
|
||||||
self.match_count = 0;
|
self.match_count = 0;
|
||||||
|
self.in_file_index = 0;
|
||||||
self.binary_byte_offset = None;
|
self.binary_byte_offset = None;
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
@@ -956,6 +994,8 @@ impl<'a, M: Matcher, W: WriteColor> StandardImpl<'a, M, W> {
|
|||||||
self.sunk.absolute_byte_offset(),
|
self.sunk.absolute_byte_offset(),
|
||||||
self.sunk.line_number(),
|
self.sunk.line_number(),
|
||||||
None,
|
None,
|
||||||
|
None,
|
||||||
|
self.in_file_index(),
|
||||||
)?;
|
)?;
|
||||||
self.write_line(self.sunk.bytes())
|
self.write_line(self.sunk.bytes())
|
||||||
}
|
}
|
||||||
@@ -974,6 +1014,10 @@ impl<'a, M: Matcher, W: WriteColor> StandardImpl<'a, M, W> {
|
|||||||
// instead.
|
// instead.
|
||||||
debug_assert!(self.multi_line());
|
debug_assert!(self.multi_line());
|
||||||
|
|
||||||
|
if self.config().squash != SquashMode::None {
|
||||||
|
return self.sink_fast_multi_line_squash();
|
||||||
|
}
|
||||||
|
|
||||||
let line_term = self.searcher.line_terminator().as_byte();
|
let line_term = self.searcher.line_terminator().as_byte();
|
||||||
let mut absolute_byte_offset = self.sunk.absolute_byte_offset();
|
let mut absolute_byte_offset = self.sunk.absolute_byte_offset();
|
||||||
for (i, line) in self.sunk.lines(line_term).enumerate() {
|
for (i, line) in self.sunk.lines(line_term).enumerate() {
|
||||||
@@ -981,6 +1025,8 @@ impl<'a, M: Matcher, W: WriteColor> StandardImpl<'a, M, W> {
|
|||||||
absolute_byte_offset,
|
absolute_byte_offset,
|
||||||
self.sunk.line_number().map(|n| n + i as u64),
|
self.sunk.line_number().map(|n| n + i as u64),
|
||||||
None,
|
None,
|
||||||
|
None,
|
||||||
|
self.in_file_index(),
|
||||||
)?;
|
)?;
|
||||||
absolute_byte_offset += line.len() as u64;
|
absolute_byte_offset += line.len() as u64;
|
||||||
|
|
||||||
@@ -989,6 +1035,20 @@ impl<'a, M: Matcher, W: WriteColor> StandardImpl<'a, M, W> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn sink_fast_multi_line_squash(&self) -> io::Result<()> {
|
||||||
|
let bytes = self.sunk.bytes();
|
||||||
|
let (line_number, line_number_end) =
|
||||||
|
self.line_range(self.sunk.line_number(), bytes);
|
||||||
|
self.write_prelude(
|
||||||
|
self.sunk.absolute_byte_offset(),
|
||||||
|
line_number,
|
||||||
|
line_number_end,
|
||||||
|
None,
|
||||||
|
self.in_file_index(),
|
||||||
|
)?;
|
||||||
|
self.write_line(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
/// Print a matching line where the configuration of the printer requires
|
/// Print a matching line where the configuration of the printer requires
|
||||||
/// finding each individual match (e.g., for coloring).
|
/// finding each individual match (e.g., for coloring).
|
||||||
fn sink_slow(&self) -> io::Result<()> {
|
fn sink_slow(&self) -> io::Result<()> {
|
||||||
@@ -1000,7 +1060,9 @@ impl<'a, M: Matcher, W: WriteColor> StandardImpl<'a, M, W> {
|
|||||||
self.write_prelude(
|
self.write_prelude(
|
||||||
self.sunk.absolute_byte_offset() + m.start() as u64,
|
self.sunk.absolute_byte_offset() + m.start() as u64,
|
||||||
self.sunk.line_number(),
|
self.sunk.line_number(),
|
||||||
|
None,
|
||||||
Some(m.start() as u64 + 1),
|
Some(m.start() as u64 + 1),
|
||||||
|
self.in_file_index(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let buf = &self.sunk.bytes()[m];
|
let buf = &self.sunk.bytes()[m];
|
||||||
@@ -1011,7 +1073,9 @@ impl<'a, M: Matcher, W: WriteColor> StandardImpl<'a, M, W> {
|
|||||||
self.write_prelude(
|
self.write_prelude(
|
||||||
self.sunk.absolute_byte_offset() + m.start() as u64,
|
self.sunk.absolute_byte_offset() + m.start() as u64,
|
||||||
self.sunk.line_number(),
|
self.sunk.line_number(),
|
||||||
|
None,
|
||||||
Some(m.start() as u64 + 1),
|
Some(m.start() as u64 + 1),
|
||||||
|
self.in_file_index(),
|
||||||
)?;
|
)?;
|
||||||
self.write_colored_line(&[m], self.sunk.bytes())?;
|
self.write_colored_line(&[m], self.sunk.bytes())?;
|
||||||
}
|
}
|
||||||
@@ -1019,7 +1083,9 @@ impl<'a, M: Matcher, W: WriteColor> StandardImpl<'a, M, W> {
|
|||||||
self.write_prelude(
|
self.write_prelude(
|
||||||
self.sunk.absolute_byte_offset(),
|
self.sunk.absolute_byte_offset(),
|
||||||
self.sunk.line_number(),
|
self.sunk.line_number(),
|
||||||
|
None,
|
||||||
Some(self.sunk.matches()[0].start() as u64 + 1),
|
Some(self.sunk.matches()[0].start() as u64 + 1),
|
||||||
|
self.in_file_index(),
|
||||||
)?;
|
)?;
|
||||||
self.write_colored_line(self.sunk.matches(), self.sunk.bytes())?;
|
self.write_colored_line(self.sunk.matches(), self.sunk.bytes())?;
|
||||||
}
|
}
|
||||||
@@ -1030,6 +1096,14 @@ impl<'a, M: Matcher, W: WriteColor> StandardImpl<'a, M, W> {
|
|||||||
debug_assert!(!self.sunk.matches().is_empty());
|
debug_assert!(!self.sunk.matches().is_empty());
|
||||||
debug_assert!(self.multi_line());
|
debug_assert!(self.multi_line());
|
||||||
|
|
||||||
|
if self.config().squash != SquashMode::None {
|
||||||
|
if self.config().only_matching {
|
||||||
|
return self.sink_slow_multi_line_only_matching_squash();
|
||||||
|
} else if self.config().per_match {
|
||||||
|
return self.sink_slow_multi_per_match_squash();
|
||||||
|
}
|
||||||
|
return self.sink_slow_multi_line_squash();
|
||||||
|
}
|
||||||
if self.config().only_matching {
|
if self.config().only_matching {
|
||||||
return self.sink_slow_multi_line_only_matching();
|
return self.sink_slow_multi_line_only_matching();
|
||||||
} else if self.config().per_match {
|
} else if self.config().per_match {
|
||||||
@@ -1047,7 +1121,9 @@ impl<'a, M: Matcher, W: WriteColor> StandardImpl<'a, M, W> {
|
|||||||
self.write_prelude(
|
self.write_prelude(
|
||||||
self.sunk.absolute_byte_offset() + line.start() as u64,
|
self.sunk.absolute_byte_offset() + line.start() as u64,
|
||||||
self.sunk.line_number().map(|n| n + count),
|
self.sunk.line_number().map(|n| n + count),
|
||||||
|
None,
|
||||||
Some(matches[0].start() as u64 + 1),
|
Some(matches[0].start() as u64 + 1),
|
||||||
|
self.in_file_index(),
|
||||||
)?;
|
)?;
|
||||||
count += 1;
|
count += 1;
|
||||||
self.trim_ascii_prefix(bytes, &mut line);
|
self.trim_ascii_prefix(bytes, &mut line);
|
||||||
@@ -1061,6 +1137,20 @@ impl<'a, M: Matcher, W: WriteColor> StandardImpl<'a, M, W> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn sink_slow_multi_line_squash(&self) -> io::Result<()> {
|
||||||
|
let bytes = self.sunk.bytes();
|
||||||
|
let (line_number, line_number_end) =
|
||||||
|
self.line_range(self.sunk.line_number(), bytes);
|
||||||
|
self.write_prelude(
|
||||||
|
self.sunk.absolute_byte_offset(),
|
||||||
|
line_number,
|
||||||
|
line_number_end,
|
||||||
|
Some(self.sunk.matches()[0].start() as u64 + 1),
|
||||||
|
self.in_file_index(),
|
||||||
|
)?;
|
||||||
|
self.write_colored_line(self.sunk.matches(), bytes)
|
||||||
|
}
|
||||||
|
|
||||||
fn sink_slow_multi_line_only_matching(&self) -> io::Result<()> {
|
fn sink_slow_multi_line_only_matching(&self) -> io::Result<()> {
|
||||||
let line_term = self.searcher.line_terminator().as_byte();
|
let line_term = self.searcher.line_terminator().as_byte();
|
||||||
let spec = self.config().colors.matched();
|
let spec = self.config().colors.matched();
|
||||||
@@ -1092,7 +1182,9 @@ impl<'a, M: Matcher, W: WriteColor> StandardImpl<'a, M, W> {
|
|||||||
self.write_prelude(
|
self.write_prelude(
|
||||||
self.sunk.absolute_byte_offset() + m.start() as u64,
|
self.sunk.absolute_byte_offset() + m.start() as u64,
|
||||||
self.sunk.line_number().map(|n| n + count),
|
self.sunk.line_number().map(|n| n + count),
|
||||||
|
None,
|
||||||
Some(m.start() as u64 + 1),
|
Some(m.start() as u64 + 1),
|
||||||
|
self.in_file_index(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let this_line = line.with_end(upto);
|
let this_line = line.with_end(upto);
|
||||||
@@ -1112,6 +1204,30 @@ impl<'a, M: Matcher, W: WriteColor> StandardImpl<'a, M, W> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn sink_slow_multi_line_only_matching_squash(&self) -> io::Result<()> {
|
||||||
|
let bytes = self.sunk.bytes();
|
||||||
|
for &m in self.sunk.matches() {
|
||||||
|
let line_start = self.line_number_for_offset(
|
||||||
|
self.sunk.line_number(),
|
||||||
|
bytes,
|
||||||
|
m.start(),
|
||||||
|
);
|
||||||
|
let (line_number, line_number_end) =
|
||||||
|
self.line_range(line_start, &bytes[m]);
|
||||||
|
self.write_prelude(
|
||||||
|
self.sunk.absolute_byte_offset() + m.start() as u64,
|
||||||
|
line_number,
|
||||||
|
line_number_end,
|
||||||
|
Some(m.start() as u64 + 1),
|
||||||
|
self.in_file_index(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let buf = &bytes[m];
|
||||||
|
self.write_colored_line(&[Match::new(0, buf.len())], buf)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn sink_slow_multi_per_match(&self) -> io::Result<()> {
|
fn sink_slow_multi_per_match(&self) -> io::Result<()> {
|
||||||
let line_term = self.searcher.line_terminator().as_byte();
|
let line_term = self.searcher.line_terminator().as_byte();
|
||||||
let spec = self.config().colors.matched();
|
let spec = self.config().colors.matched();
|
||||||
@@ -1130,7 +1246,9 @@ impl<'a, M: Matcher, W: WriteColor> StandardImpl<'a, M, W> {
|
|||||||
self.write_prelude(
|
self.write_prelude(
|
||||||
self.sunk.absolute_byte_offset() + line.start() as u64,
|
self.sunk.absolute_byte_offset() + line.start() as u64,
|
||||||
self.sunk.line_number().map(|n| n + count),
|
self.sunk.line_number().map(|n| n + count),
|
||||||
|
None,
|
||||||
Some(m.start().saturating_sub(line.start()) as u64 + 1),
|
Some(m.start().saturating_sub(line.start()) as u64 + 1),
|
||||||
|
self.in_file_index(),
|
||||||
)?;
|
)?;
|
||||||
count += 1;
|
count += 1;
|
||||||
self.trim_line_terminator(bytes, &mut line);
|
self.trim_line_terminator(bytes, &mut line);
|
||||||
@@ -1169,6 +1287,31 @@ impl<'a, M: Matcher, W: WriteColor> StandardImpl<'a, M, W> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn sink_slow_multi_per_match_squash(&self) -> io::Result<()> {
|
||||||
|
let bytes = self.sunk.bytes();
|
||||||
|
for &m in self.sunk.matches() {
|
||||||
|
let line_start = self.line_number_for_offset(
|
||||||
|
self.sunk.line_number(),
|
||||||
|
bytes,
|
||||||
|
m.start(),
|
||||||
|
);
|
||||||
|
let (line_number, line_number_end) =
|
||||||
|
self.line_range(line_start, &bytes[m]);
|
||||||
|
let column = self.column_number_for_offset(bytes, m.start());
|
||||||
|
self.write_prelude(
|
||||||
|
self.sunk.absolute_byte_offset() + m.start() as u64,
|
||||||
|
line_number,
|
||||||
|
line_number_end,
|
||||||
|
Some(column),
|
||||||
|
self.in_file_index(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let buf = &bytes[m];
|
||||||
|
self.write_colored_line(&[Match::new(0, buf.len())], buf)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Write the beginning part of a matching line. This (may) include things
|
/// Write the beginning part of a matching line. This (may) include things
|
||||||
/// like the file path, line number among others, depending on the
|
/// like the file path, line number among others, depending on the
|
||||||
/// configuration and the parameters given.
|
/// configuration and the parameters given.
|
||||||
@@ -1177,12 +1320,14 @@ impl<'a, M: Matcher, W: WriteColor> StandardImpl<'a, M, W> {
|
|||||||
&self,
|
&self,
|
||||||
absolute_byte_offset: u64,
|
absolute_byte_offset: u64,
|
||||||
line_number: Option<u64>,
|
line_number: Option<u64>,
|
||||||
|
line_number_end: Option<u64>,
|
||||||
column: Option<u64>,
|
column: Option<u64>,
|
||||||
|
in_file_index: Option<u64>,
|
||||||
) -> io::Result<()> {
|
) -> io::Result<()> {
|
||||||
let mut prelude = PreludeWriter::new(self);
|
let mut prelude = PreludeWriter::new(self);
|
||||||
prelude.start(line_number, column)?;
|
prelude.start(line_number, column)?;
|
||||||
prelude.write_path()?;
|
prelude.write_path(in_file_index)?;
|
||||||
prelude.write_line_number(line_number)?;
|
prelude.write_line_number(line_number, line_number_end)?;
|
||||||
prelude.write_column_number(column)?;
|
prelude.write_column_number(column)?;
|
||||||
prelude.write_byte_offset(absolute_byte_offset)?;
|
prelude.write_byte_offset(absolute_byte_offset)?;
|
||||||
prelude.end()
|
prelude.end()
|
||||||
@@ -1206,12 +1351,20 @@ impl<'a, M: Matcher, W: WriteColor> StandardImpl<'a, M, W> {
|
|||||||
self.sunk.matches(),
|
self.sunk.matches(),
|
||||||
&mut 0,
|
&mut 0,
|
||||||
)?;
|
)?;
|
||||||
} else {
|
} else if self.config().squash == SquashMode::None {
|
||||||
// self.write_trim(line)?;
|
// self.write_trim(line)?;
|
||||||
self.write(line)?;
|
self.write(line)?;
|
||||||
if !self.has_line_terminator(line) {
|
if !self.has_line_terminator(line) {
|
||||||
self.write_line_term()?;
|
self.write_line_term()?;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
let mut range = Match::new(0, line.len());
|
||||||
|
self.trim_line_terminator(line, &mut range);
|
||||||
|
let line = &line[range];
|
||||||
|
let mut squasher = SquashState::new();
|
||||||
|
self.write_squashed(line, &mut squasher)?;
|
||||||
|
squasher.finish(self)?;
|
||||||
|
self.write_line_term()?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -1232,7 +1385,11 @@ impl<'a, M: Matcher, W: WriteColor> StandardImpl<'a, M, W> {
|
|||||||
if self.exceeds_max_columns(bytes) {
|
if self.exceeds_max_columns(bytes) {
|
||||||
self.write_exceeded_line(bytes, line, matches, &mut 0)
|
self.write_exceeded_line(bytes, line, matches, &mut 0)
|
||||||
} else {
|
} else {
|
||||||
self.write_colored_matches(bytes, line, matches, &mut 0)?;
|
if self.config().squash == SquashMode::None {
|
||||||
|
self.write_colored_matches(bytes, line, matches, &mut 0)?;
|
||||||
|
} else {
|
||||||
|
self.write_colored_matches_squashed(bytes, line, matches, &mut 0)?;
|
||||||
|
}
|
||||||
self.write_line_term()?;
|
self.write_line_term()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -1287,6 +1444,135 @@ impl<'a, M: Matcher, W: WriteColor> StandardImpl<'a, M, W> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn write_colored_matches_squashed(
|
||||||
|
&self,
|
||||||
|
bytes: &[u8],
|
||||||
|
mut line: Match,
|
||||||
|
matches: &[Match],
|
||||||
|
match_index: &mut usize,
|
||||||
|
) -> io::Result<()> {
|
||||||
|
self.trim_line_terminator(bytes, &mut line);
|
||||||
|
let mut squasher = SquashState::new();
|
||||||
|
if matches.is_empty() {
|
||||||
|
self.write_squashed(&bytes[line], &mut squasher)?;
|
||||||
|
squasher.finish(self)?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
self.start_line_highlight()?;
|
||||||
|
while !line.is_empty() {
|
||||||
|
if matches[*match_index].end() <= line.start() {
|
||||||
|
if *match_index + 1 < matches.len() {
|
||||||
|
*match_index += 1;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
self.end_color_match()?;
|
||||||
|
self.write_squashed(&bytes[line], &mut squasher)?;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let m = matches[*match_index];
|
||||||
|
if line.start() < m.start() {
|
||||||
|
let upto = cmp::min(line.end(), m.start());
|
||||||
|
self.end_color_match()?;
|
||||||
|
self.write_squashed(
|
||||||
|
&bytes[line.with_end(upto)],
|
||||||
|
&mut squasher,
|
||||||
|
)?;
|
||||||
|
line = line.with_start(upto);
|
||||||
|
} else {
|
||||||
|
let upto = cmp::min(line.end(), m.end());
|
||||||
|
self.start_color_match()?;
|
||||||
|
self.write_squashed(
|
||||||
|
&bytes[line.with_end(upto)],
|
||||||
|
&mut squasher,
|
||||||
|
)?;
|
||||||
|
line = line.with_start(upto);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.end_color_match()?;
|
||||||
|
self.end_line_highlight()?;
|
||||||
|
squasher.finish(self)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_squashed(
|
||||||
|
&self,
|
||||||
|
bytes: &[u8],
|
||||||
|
squasher: &mut SquashState,
|
||||||
|
) -> io::Result<()> {
|
||||||
|
match self.config().squash {
|
||||||
|
SquashMode::None => self.write(bytes),
|
||||||
|
SquashMode::Whitespace => self.write_squashed_whitespace(bytes, squasher),
|
||||||
|
SquashMode::Newlines => self.write_squashed_newlines(bytes, squasher),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_squashed_whitespace(
|
||||||
|
&self,
|
||||||
|
bytes: &[u8],
|
||||||
|
squasher: &mut SquashState,
|
||||||
|
) -> io::Result<()> {
|
||||||
|
let line_term = self.searcher.line_terminator();
|
||||||
|
let mut iter = bytes.char_indices();
|
||||||
|
while let Some((start, end, ch)) = iter.next() {
|
||||||
|
let is_line_term = !line_term.is_crlf()
|
||||||
|
&& bytes[start] == line_term.as_byte();
|
||||||
|
if ch.is_whitespace() || is_line_term {
|
||||||
|
squasher.pending_space = true;
|
||||||
|
} else {
|
||||||
|
squasher.flush(self)?;
|
||||||
|
self.write(&bytes[start..end])?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_squashed_newlines(
|
||||||
|
&self,
|
||||||
|
bytes: &[u8],
|
||||||
|
squasher: &mut SquashState,
|
||||||
|
) -> io::Result<()> {
|
||||||
|
let line_term = self.searcher.line_terminator();
|
||||||
|
let mut last = 0;
|
||||||
|
let mut i = 0;
|
||||||
|
while i < bytes.len() {
|
||||||
|
let mut newline_start = None;
|
||||||
|
let mut newline_end = 0;
|
||||||
|
if line_term.is_crlf()
|
||||||
|
&& bytes[i] == b'\r'
|
||||||
|
&& i + 1 < bytes.len()
|
||||||
|
&& bytes[i + 1] == b'\n'
|
||||||
|
{
|
||||||
|
newline_start = Some(i);
|
||||||
|
newline_end = i + 2;
|
||||||
|
} else if line_term.is_crlf() && bytes[i] == b'\n' {
|
||||||
|
newline_start = Some(i);
|
||||||
|
newline_end = i + 1;
|
||||||
|
} else if !line_term.is_crlf() && bytes[i] == line_term.as_byte() {
|
||||||
|
newline_start = Some(i);
|
||||||
|
newline_end = i + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(start) = newline_start {
|
||||||
|
if last < start {
|
||||||
|
squasher.flush(self)?;
|
||||||
|
self.write(&bytes[last..start])?;
|
||||||
|
}
|
||||||
|
squasher.pending_space = true;
|
||||||
|
i = newline_end;
|
||||||
|
last = newline_end;
|
||||||
|
} else {
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if last < bytes.len() {
|
||||||
|
squasher.flush(self)?;
|
||||||
|
self.write(&bytes[last..])?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn write_exceeded_line(
|
fn write_exceeded_line(
|
||||||
&self,
|
&self,
|
||||||
bytes: &[u8],
|
bytes: &[u8],
|
||||||
@@ -1532,6 +1818,14 @@ impl<'a, M: Matcher, W: WriteColor> StandardImpl<'a, M, W> {
|
|||||||
self.sunk.context_kind().is_some()
|
self.sunk.context_kind().is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn in_file_index(&self) -> Option<u64> {
|
||||||
|
if self.is_context() || !self.config().in_file_index {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(self.sink.in_file_index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Return the underlying configuration for this printer.
|
/// Return the underlying configuration for this printer.
|
||||||
fn config(&self) -> &'a Config {
|
fn config(&self) -> &'a Config {
|
||||||
&self.sink.standard.config
|
&self.sink.standard.config
|
||||||
@@ -1563,6 +1857,56 @@ impl<'a, M: Matcher, W: WriteColor> StandardImpl<'a, M, W> {
|
|||||||
self.config().max_columns.map_or(false, |m| line.len() as u64 > m)
|
self.config().max_columns.map_or(false, |m| line.len() as u64 > m)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn line_span(&self, bytes: &[u8]) -> u64 {
|
||||||
|
if bytes.is_empty() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
let line_term = self.searcher.line_terminator().as_byte();
|
||||||
|
let count = bytes.iter().filter(|&&b| b == line_term).count() as u64;
|
||||||
|
let ends_with_term = bytes.last().map_or(false, |&b| b == line_term);
|
||||||
|
let lines = if ends_with_term { count } else { count + 1 };
|
||||||
|
lines.saturating_sub(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn line_range(
|
||||||
|
&self,
|
||||||
|
line_start: Option<u64>,
|
||||||
|
bytes: &[u8],
|
||||||
|
) -> (Option<u64>, Option<u64>) {
|
||||||
|
let Some(start) = line_start else { return (None, None) };
|
||||||
|
let end = start + self.line_span(bytes);
|
||||||
|
if end > start {
|
||||||
|
(Some(start), Some(end))
|
||||||
|
} else {
|
||||||
|
(Some(start), None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn line_number_for_offset(
|
||||||
|
&self,
|
||||||
|
line_start: Option<u64>,
|
||||||
|
bytes: &[u8],
|
||||||
|
offset: usize,
|
||||||
|
) -> Option<u64> {
|
||||||
|
let line_start = line_start?;
|
||||||
|
let line_term = self.searcher.line_terminator().as_byte();
|
||||||
|
let count = bytes[..offset]
|
||||||
|
.iter()
|
||||||
|
.filter(|&&b| b == line_term)
|
||||||
|
.count() as u64;
|
||||||
|
Some(line_start + count)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn column_number_for_offset(&self, bytes: &[u8], offset: usize) -> u64 {
|
||||||
|
let line_term = self.searcher.line_terminator().as_byte();
|
||||||
|
let line_start = bytes[..offset]
|
||||||
|
.iter()
|
||||||
|
.rposition(|&b| b == line_term)
|
||||||
|
.map(|pos| pos + 1)
|
||||||
|
.unwrap_or(0);
|
||||||
|
(offset - line_start) as u64 + 1
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns true if and only if the searcher may report matches over
|
/// Returns true if and only if the searcher may report matches over
|
||||||
/// multiple lines.
|
/// multiple lines.
|
||||||
///
|
///
|
||||||
@@ -1588,6 +1932,35 @@ impl<'a, M: Matcher, W: WriteColor> StandardImpl<'a, M, W> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
struct SquashState {
|
||||||
|
pending_space: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SquashState {
|
||||||
|
fn new() -> SquashState {
|
||||||
|
SquashState { pending_space: false }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush<M: Matcher, W: WriteColor>(
|
||||||
|
&mut self,
|
||||||
|
std: &StandardImpl<'_, M, W>,
|
||||||
|
) -> io::Result<()> {
|
||||||
|
if self.pending_space {
|
||||||
|
std.write(b" ")?;
|
||||||
|
self.pending_space = false;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finish<M: Matcher, W: WriteColor>(
|
||||||
|
&mut self,
|
||||||
|
std: &StandardImpl<'_, M, W>,
|
||||||
|
) -> io::Result<()> {
|
||||||
|
self.flush(std)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A writer for the prelude (the beginning part of a matching line).
|
/// A writer for the prelude (the beginning part of a matching line).
|
||||||
///
|
///
|
||||||
/// This encapsulates the state needed to print the prelude.
|
/// This encapsulates the state needed to print the prelude.
|
||||||
@@ -1657,16 +2030,27 @@ impl<'a, M: Matcher, W: WriteColor> PreludeWriter<'a, M, W> {
|
|||||||
/// separator. (If a path terminator is set, then that is used instead of
|
/// separator. (If a path terminator is set, then that is used instead of
|
||||||
/// the field separator.)
|
/// the field separator.)
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn write_path(&mut self) -> io::Result<()> {
|
fn write_path(&mut self, in_file_index: Option<u64>) -> io::Result<()> {
|
||||||
// The prelude doesn't handle headings, only what comes before a match
|
// The prelude doesn't handle headings, only what comes before a match
|
||||||
// on the same line. So if we are emitting paths in headings, we should
|
// on the same line. So if we are emitting paths in headings, we should
|
||||||
// not do it here on each line.
|
// not do it here on each line.
|
||||||
if self.config().heading {
|
if self.config().heading && in_file_index.is_none() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let path = self.std.path();
|
||||||
|
if path.is_none() && in_file_index.is_none() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
let Some(path) = self.std.path() else { return Ok(()) };
|
|
||||||
self.write_separator()?;
|
self.write_separator()?;
|
||||||
self.std.write_path(path)?;
|
if let Some(path) = path {
|
||||||
|
self.std.write_path(path)?;
|
||||||
|
}
|
||||||
|
if let Some(index) = in_file_index {
|
||||||
|
self.std.write_spec(self.config().colors.path(), b"[")?;
|
||||||
|
let n = DecimalFormatter::new(index);
|
||||||
|
self.std.write_spec(self.config().colors.path(), n.as_bytes())?;
|
||||||
|
self.std.write_spec(self.config().colors.path(), b"]")?;
|
||||||
|
}
|
||||||
|
|
||||||
self.next_separator = if self.config().path_terminator.is_some() {
|
self.next_separator = if self.config().path_terminator.is_some() {
|
||||||
PreludeSeparator::PathTerminator
|
PreludeSeparator::PathTerminator
|
||||||
@@ -1678,11 +2062,20 @@ impl<'a, M: Matcher, W: WriteColor> PreludeWriter<'a, M, W> {
|
|||||||
|
|
||||||
/// Writes the line number field if present.
|
/// Writes the line number field if present.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn write_line_number(&mut self, line: Option<u64>) -> io::Result<()> {
|
fn write_line_number(
|
||||||
|
&mut self,
|
||||||
|
line: Option<u64>,
|
||||||
|
line_end: Option<u64>,
|
||||||
|
) -> io::Result<()> {
|
||||||
let Some(line_number) = line else { return Ok(()) };
|
let Some(line_number) = line else { return Ok(()) };
|
||||||
self.write_separator()?;
|
self.write_separator()?;
|
||||||
let n = DecimalFormatter::new(line_number);
|
let n = DecimalFormatter::new(line_number);
|
||||||
self.std.write_spec(self.config().colors.line(), n.as_bytes())?;
|
self.std.write_spec(self.config().colors.line(), n.as_bytes())?;
|
||||||
|
if let Some(end) = line_end {
|
||||||
|
self.std.write_spec(self.config().colors.line(), b"-")?;
|
||||||
|
let n = DecimalFormatter::new(end);
|
||||||
|
self.std.write_spec(self.config().colors.line(), n.as_bytes())?;
|
||||||
|
}
|
||||||
self.next_separator = PreludeSeparator::FieldSeparator;
|
self.next_separator = PreludeSeparator::FieldSeparator;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -2365,6 +2758,50 @@ Watson
|
|||||||
assert_eq_printed!(expected, got);
|
assert_eq_printed!(expected, got);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn squash_multi_line_range() {
|
||||||
|
let matcher = RegexMatcher::new("(?s)line 1\\nline 2").unwrap();
|
||||||
|
let mut printer = StandardBuilder::new()
|
||||||
|
.squash(SquashMode::Newlines)
|
||||||
|
.build(NoColor::new(vec![]));
|
||||||
|
SearcherBuilder::new()
|
||||||
|
.line_number(true)
|
||||||
|
.multi_line(true)
|
||||||
|
.build()
|
||||||
|
.search_reader(
|
||||||
|
&matcher,
|
||||||
|
b"line 1\nline 2\n",
|
||||||
|
printer.sink(&matcher),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let got = printer_contents(&mut printer);
|
||||||
|
let expected = "1-2:line 1 line 2\n";
|
||||||
|
assert_eq_printed!(expected, got);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn squash_whitespace_multi_line() {
|
||||||
|
let matcher = RegexMatcher::new("(?s)line\\s+2").unwrap();
|
||||||
|
let mut printer = StandardBuilder::new()
|
||||||
|
.squash(SquashMode::Whitespace)
|
||||||
|
.build(NoColor::new(vec![]));
|
||||||
|
SearcherBuilder::new()
|
||||||
|
.line_number(true)
|
||||||
|
.multi_line(true)
|
||||||
|
.build()
|
||||||
|
.search_reader(
|
||||||
|
&matcher,
|
||||||
|
b"line\t\n 2\n",
|
||||||
|
printer.sink(&matcher),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let got = printer_contents(&mut printer);
|
||||||
|
let expected = "1-2:line 2\n";
|
||||||
|
assert_eq_printed!(expected, got);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn column_number() {
|
fn column_number() {
|
||||||
let matcher = RegexMatcher::new("Watson").unwrap();
|
let matcher = RegexMatcher::new("Watson").unwrap();
|
||||||
|
|||||||
@@ -212,6 +212,18 @@ impl<'s, M: Matcher, S: Sink> Core<'s, M, S> {
|
|||||||
consumed
|
consumed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn advance_buffer(&mut self, buf: &[u8], consumed: usize) {
|
||||||
|
if consumed == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.count_lines(buf, consumed);
|
||||||
|
self.absolute_byte_offset += consumed as u64;
|
||||||
|
self.last_line_counted = 0;
|
||||||
|
self.last_line_visited =
|
||||||
|
self.last_line_visited.saturating_sub(consumed);
|
||||||
|
self.set_pos(self.pos().saturating_sub(consumed));
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn detect_binary(
|
pub(crate) fn detect_binary(
|
||||||
&mut self,
|
&mut self,
|
||||||
buf: &[u8],
|
buf: &[u8],
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
use grep_matcher::Matcher;
|
use grep_matcher::Matcher;
|
||||||
|
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
line_buffer::{DEFAULT_BUFFER_CAPACITY, LineBufferReader},
|
line_buffer::{DEFAULT_BUFFER_CAPACITY, LineBufferReader, alloc_error},
|
||||||
lines::{self, LineStep},
|
lines::{self, LineStep},
|
||||||
searcher::{Config, Range, Searcher, core::Core},
|
searcher::{Config, Range, Searcher, core::Core},
|
||||||
sink::{Sink, SinkError},
|
sink::{Sink, SinkError},
|
||||||
@@ -138,6 +140,348 @@ impl<'s, M: Matcher, S: Sink> SliceByLine<'s, M, S> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct WindowedMultiLine<'s, M, S> {
|
||||||
|
config: &'s Config,
|
||||||
|
core: Core<'s, M, S>,
|
||||||
|
window_lines: usize,
|
||||||
|
buf: Vec<u8>,
|
||||||
|
buf_start: usize,
|
||||||
|
line_lens: VecDeque<usize>,
|
||||||
|
abs_start: u64,
|
||||||
|
current_index: usize,
|
||||||
|
eof: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s, M: Matcher, S: Sink> WindowedMultiLine<'s, M, S> {
|
||||||
|
pub(crate) fn new(
|
||||||
|
searcher: &'s Searcher,
|
||||||
|
matcher: M,
|
||||||
|
window_lines: usize,
|
||||||
|
write_to: S,
|
||||||
|
) -> WindowedMultiLine<'s, M, S> {
|
||||||
|
debug_assert!(searcher.multi_line_with_matcher(&matcher));
|
||||||
|
debug_assert!(window_lines > 0);
|
||||||
|
|
||||||
|
WindowedMultiLine {
|
||||||
|
config: &searcher.config,
|
||||||
|
core: Core::new(searcher, matcher, write_to, true),
|
||||||
|
window_lines,
|
||||||
|
buf: Vec::new(),
|
||||||
|
buf_start: 0,
|
||||||
|
line_lens: VecDeque::new(),
|
||||||
|
abs_start: 0,
|
||||||
|
current_index: 0,
|
||||||
|
eof: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn run_reader<R: std::io::Read>(
|
||||||
|
mut self,
|
||||||
|
mut rdr: LineBufferReader<'s, R>,
|
||||||
|
) -> Result<(), S::Error> {
|
||||||
|
if self.core.begin()? {
|
||||||
|
let mut already_binary = rdr.binary_byte_offset().is_some();
|
||||||
|
while self.fill_reader(&mut rdr, &mut already_binary)?
|
||||||
|
|| !self.line_lens.is_empty()
|
||||||
|
{
|
||||||
|
if !self.process_current_line()? {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let byte_count = self.byte_count();
|
||||||
|
let binary_byte_offset = self.core.binary_byte_offset();
|
||||||
|
self.core.finish(byte_count, binary_byte_offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn run_slice(mut self, slice: &'s [u8]) -> Result<(), S::Error> {
|
||||||
|
if self.core.begin()? {
|
||||||
|
let binary_upto =
|
||||||
|
std::cmp::min(slice.len(), DEFAULT_BUFFER_CAPACITY);
|
||||||
|
let binary_range = Range::new(0, binary_upto);
|
||||||
|
if !self.core.detect_binary(slice, &binary_range)? {
|
||||||
|
let mut stepper = LineStep::new(
|
||||||
|
self.config.line_term.as_byte(),
|
||||||
|
0,
|
||||||
|
slice.len(),
|
||||||
|
);
|
||||||
|
while let Some(line) = stepper.next_match(slice) {
|
||||||
|
self.push_line(&slice[line])?;
|
||||||
|
}
|
||||||
|
self.eof = true;
|
||||||
|
while !self.line_lens.is_empty() {
|
||||||
|
if !self.process_current_line()? {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let byte_count = self.byte_count();
|
||||||
|
let binary_byte_offset = self.core.binary_byte_offset();
|
||||||
|
self.core.finish(byte_count, binary_byte_offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fill_reader<R: std::io::Read>(
|
||||||
|
&mut self,
|
||||||
|
rdr: &mut LineBufferReader<'s, R>,
|
||||||
|
already_binary: &mut bool,
|
||||||
|
) -> Result<bool, S::Error> {
|
||||||
|
while !self.eof
|
||||||
|
&& self.line_lens.len() < self.current_index + self.window_lines
|
||||||
|
{
|
||||||
|
let didread = match rdr.fill() {
|
||||||
|
Err(err) => return Err(S::Error::error_io(err)),
|
||||||
|
Ok(didread) => didread,
|
||||||
|
};
|
||||||
|
if !*already_binary {
|
||||||
|
if let Some(offset) = rdr.binary_byte_offset() {
|
||||||
|
*already_binary = true;
|
||||||
|
if !self.core.binary_data(offset)? {
|
||||||
|
self.eof = true;
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !didread {
|
||||||
|
self.eof = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let buf = rdr.buffer();
|
||||||
|
let mut stepper = LineStep::new(
|
||||||
|
self.config.line_term.as_byte(),
|
||||||
|
0,
|
||||||
|
buf.len(),
|
||||||
|
);
|
||||||
|
while let Some(line) = stepper.next_match(buf) {
|
||||||
|
let bytes = &buf[line];
|
||||||
|
self.push_line(bytes)?;
|
||||||
|
}
|
||||||
|
rdr.consume(buf.len());
|
||||||
|
}
|
||||||
|
Ok(!self.eof)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_line(&mut self, line: &[u8]) -> Result<(), S::Error> {
|
||||||
|
self.buf.extend_from_slice(line);
|
||||||
|
self.line_lens.push_back(line.len());
|
||||||
|
if let Some(limit) = self.config.heap_limit {
|
||||||
|
let used = self.buf.len() - self.buf_start;
|
||||||
|
if used > limit {
|
||||||
|
return Err(S::Error::error_io(alloc_error(limit)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_current_line(&mut self) -> Result<bool, S::Error> {
|
||||||
|
if self.current_index >= self.line_lens.len() {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
let window_end =
|
||||||
|
std::cmp::min(self.line_lens.len(), self.current_index + self.window_lines);
|
||||||
|
let window_start_off = self.line_offset(self.current_index);
|
||||||
|
let window_end_off = self.line_offset(window_end);
|
||||||
|
let line0_len = self.line_lens[self.current_index];
|
||||||
|
|
||||||
|
{
|
||||||
|
let buffer = &self.buf[self.buf_start..];
|
||||||
|
let window_bytes =
|
||||||
|
&self.buf[self.buf_start + window_start_off
|
||||||
|
..self.buf_start + window_end_off];
|
||||||
|
if self.config.invert_match {
|
||||||
|
if !sink_inverted_line(
|
||||||
|
&mut self.core,
|
||||||
|
self.config,
|
||||||
|
buffer,
|
||||||
|
window_bytes,
|
||||||
|
window_start_off,
|
||||||
|
line0_len,
|
||||||
|
)? {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
} else if !sink_matched_line(
|
||||||
|
&mut self.core,
|
||||||
|
self.config,
|
||||||
|
buffer,
|
||||||
|
window_bytes,
|
||||||
|
window_start_off,
|
||||||
|
line0_len,
|
||||||
|
)? {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
let drop_upto = window_start_off + line0_len;
|
||||||
|
if self.config.passthru {
|
||||||
|
if !self.core.other_context_by_line(buffer, drop_upto)? {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
} else if !self.core.after_context_by_line(buffer, drop_upto)? {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.current_index += 1;
|
||||||
|
if self.current_index > self.config.before_context {
|
||||||
|
let drop_len = self.line_lens.pop_front().unwrap();
|
||||||
|
self.shift_buffer(drop_len);
|
||||||
|
self.current_index -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.eof && self.current_index >= self.line_lens.len() {
|
||||||
|
let buffer = &self.buf[self.buf_start..];
|
||||||
|
if self.config.passthru {
|
||||||
|
if !self.core.other_context_by_line(buffer, buffer.len())? {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
} else if !self.core.after_context_by_line(buffer, buffer.len())? {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn line_offset(&self, idx: usize) -> usize {
|
||||||
|
self.line_lens.iter().take(idx).sum()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn shift_buffer(&mut self, consumed: usize) {
|
||||||
|
let buffer = &self.buf[self.buf_start..];
|
||||||
|
self.core.advance_buffer(buffer, consumed);
|
||||||
|
self.buf_start += consumed;
|
||||||
|
self.abs_start += consumed as u64;
|
||||||
|
if self.buf_start > 0 && self.buf_start > self.buf.len() / 2 {
|
||||||
|
self.buf.copy_within(self.buf_start.., 0);
|
||||||
|
let new_len = self.buf.len() - self.buf_start;
|
||||||
|
self.buf.truncate(new_len);
|
||||||
|
self.buf_start = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn byte_count(&mut self) -> u64 {
|
||||||
|
match self.core.binary_byte_offset() {
|
||||||
|
Some(offset) if offset < self.core.pos() as u64 => offset,
|
||||||
|
_ => self.abs_start + (self.buf.len() - self.buf_start) as u64,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sink_matched_line<M: Matcher, S: Sink>(
|
||||||
|
core: &mut Core<'_, M, S>,
|
||||||
|
config: &Config,
|
||||||
|
buffer: &[u8],
|
||||||
|
window_bytes: &[u8],
|
||||||
|
window_start_off: usize,
|
||||||
|
line0_len: usize,
|
||||||
|
) -> Result<bool, S::Error> {
|
||||||
|
let mut pos = 0;
|
||||||
|
let mut last_match: Option<Range> = None;
|
||||||
|
while let Some(mat) = find_in_window(core, window_bytes, pos)? {
|
||||||
|
if mat.start() >= line0_len {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let line = lines::locate(
|
||||||
|
window_bytes,
|
||||||
|
config.line_term.as_byte(),
|
||||||
|
mat,
|
||||||
|
)
|
||||||
|
.offset(window_start_off);
|
||||||
|
match last_match.take() {
|
||||||
|
None => {
|
||||||
|
last_match = Some(line);
|
||||||
|
}
|
||||||
|
Some(last) => {
|
||||||
|
if last.end() >= line.start() {
|
||||||
|
last_match = Some(last.with_end(line.end()));
|
||||||
|
} else {
|
||||||
|
if !sink_context(core, config, buffer, &last)? {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
if !core.matched(buffer, &last)? {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
last_match = Some(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pos = mat.end();
|
||||||
|
if mat.is_empty() && pos < window_bytes.len() {
|
||||||
|
pos += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(last) = last_match.take() {
|
||||||
|
if !sink_context(core, config, buffer, &last)? {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
if !core.matched(buffer, &last)? {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sink_inverted_line<M: Matcher, S: Sink>(
|
||||||
|
core: &mut Core<'_, M, S>,
|
||||||
|
config: &Config,
|
||||||
|
buffer: &[u8],
|
||||||
|
window_bytes: &[u8],
|
||||||
|
window_start_off: usize,
|
||||||
|
line0_len: usize,
|
||||||
|
) -> Result<bool, S::Error> {
|
||||||
|
let mut pos = 0;
|
||||||
|
while let Some(mat) = find_in_window(core, window_bytes, pos)? {
|
||||||
|
if mat.start() >= line0_len {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if mat.start() < line0_len {
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
pos = mat.end();
|
||||||
|
if mat.is_empty() && pos < window_bytes.len() {
|
||||||
|
pos += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let line = Range::new(window_start_off, window_start_off + line0_len);
|
||||||
|
if !sink_context(core, config, buffer, &line)? {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
if !core.matched(buffer, &line)? {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_in_window<M: Matcher, S: Sink>(
|
||||||
|
core: &mut Core<'_, M, S>,
|
||||||
|
window_bytes: &[u8],
|
||||||
|
pos: usize,
|
||||||
|
) -> Result<Option<Range>, S::Error> {
|
||||||
|
core.find(&window_bytes[pos..])
|
||||||
|
.map(|m| m.map(|m| m.offset(pos)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sink_context<M: Matcher, S: Sink>(
|
||||||
|
core: &mut Core<'_, M, S>,
|
||||||
|
config: &Config,
|
||||||
|
buffer: &[u8],
|
||||||
|
range: &Range,
|
||||||
|
) -> Result<bool, S::Error> {
|
||||||
|
if config.passthru {
|
||||||
|
if !core.other_context_by_line(buffer, range.start())? {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if !core.after_context_by_line(buffer, range.start())? {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
if !core.before_context_by_line(buffer, range.start())? {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct MultiLine<'s, M, S> {
|
pub(crate) struct MultiLine<'s, M, S> {
|
||||||
config: &'s Config,
|
config: &'s Config,
|
||||||
@@ -518,6 +862,37 @@ byte count:366
|
|||||||
.test();
|
.test();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn multi_line_window_limits_match() {
|
||||||
|
let haystack = "a\nb\nc\nd\n";
|
||||||
|
let matcher = RegexMatcher::new("a\nb\nc");
|
||||||
|
|
||||||
|
let mut builder = SearcherBuilder::new();
|
||||||
|
builder.multi_line(true).multiline_window(Some(2)).line_number(false);
|
||||||
|
let mut sink = KitchenSink::new();
|
||||||
|
let mut searcher = builder.build();
|
||||||
|
searcher
|
||||||
|
.search_slice(&matcher, haystack.as_bytes(), &mut sink)
|
||||||
|
.unwrap();
|
||||||
|
let got = String::from_utf8(sink.as_bytes().to_vec()).unwrap();
|
||||||
|
let exp = format!("\nbyte count:{}\n", haystack.len());
|
||||||
|
assert_eq!(exp, got);
|
||||||
|
|
||||||
|
let mut builder = SearcherBuilder::new();
|
||||||
|
builder.multi_line(true).multiline_window(Some(3)).line_number(false);
|
||||||
|
let mut sink = KitchenSink::new();
|
||||||
|
let mut searcher = builder.build();
|
||||||
|
searcher
|
||||||
|
.search_slice(&matcher, haystack.as_bytes(), &mut sink)
|
||||||
|
.unwrap();
|
||||||
|
let exp = format!(
|
||||||
|
"0:a\n2:b\n4:c\n\nbyte count:{}\n",
|
||||||
|
haystack.len()
|
||||||
|
);
|
||||||
|
let got = String::from_utf8(sink.as_bytes().to_vec()).unwrap();
|
||||||
|
assert_eq!(exp, got);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn multi_line_overlap2() {
|
fn multi_line_overlap2() {
|
||||||
let haystack = "xxx\nabc\ndefabc\ndefxxx\nxxx";
|
let haystack = "xxx\nabc\ndefabc\ndefxxx\nxxx";
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ use crate::{
|
|||||||
self, BufferAllocation, DEFAULT_BUFFER_CAPACITY, LineBuffer,
|
self, BufferAllocation, DEFAULT_BUFFER_CAPACITY, LineBuffer,
|
||||||
LineBufferBuilder, LineBufferReader, alloc_error,
|
LineBufferBuilder, LineBufferReader, alloc_error,
|
||||||
},
|
},
|
||||||
searcher::glue::{MultiLine, ReadByLine, SliceByLine},
|
searcher::glue::{MultiLine, ReadByLine, SliceByLine, WindowedMultiLine},
|
||||||
sink::{Sink, SinkError},
|
sink::{Sink, SinkError},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -172,6 +172,8 @@ pub struct Config {
|
|||||||
binary: BinaryDetection,
|
binary: BinaryDetection,
|
||||||
/// Whether to enable matching across multiple lines.
|
/// Whether to enable matching across multiple lines.
|
||||||
multi_line: bool,
|
multi_line: bool,
|
||||||
|
/// The maximum number of lines a multi-line match may span.
|
||||||
|
multiline_window: Option<usize>,
|
||||||
/// An encoding that, when present, causes the searcher to transcode all
|
/// An encoding that, when present, causes the searcher to transcode all
|
||||||
/// input from the encoding to UTF-8.
|
/// input from the encoding to UTF-8.
|
||||||
encoding: Option<Encoding>,
|
encoding: Option<Encoding>,
|
||||||
@@ -197,6 +199,7 @@ impl Default for Config {
|
|||||||
mmap: MmapChoice::default(),
|
mmap: MmapChoice::default(),
|
||||||
binary: BinaryDetection::default(),
|
binary: BinaryDetection::default(),
|
||||||
multi_line: false,
|
multi_line: false,
|
||||||
|
multiline_window: None,
|
||||||
encoding: None,
|
encoding: None,
|
||||||
bom_sniffing: true,
|
bom_sniffing: true,
|
||||||
stop_on_nonmatch: false,
|
stop_on_nonmatch: false,
|
||||||
@@ -390,6 +393,15 @@ impl SearcherBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Limit multi-line matches to a window of at most `line_count` lines.
|
||||||
|
pub fn multiline_window(
|
||||||
|
&mut self,
|
||||||
|
line_count: Option<usize>,
|
||||||
|
) -> &mut SearcherBuilder {
|
||||||
|
self.config.multiline_window = line_count;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Whether to include a fixed number of lines after every match.
|
/// Whether to include a fixed number of lines after every match.
|
||||||
///
|
///
|
||||||
/// When this is set to a non-zero number, then the searcher will report
|
/// When this is set to a non-zero number, then the searcher will report
|
||||||
@@ -694,6 +706,13 @@ impl Searcher {
|
|||||||
// enabled. This pre-allocates a buffer roughly the size of the file,
|
// enabled. This pre-allocates a buffer roughly the size of the file,
|
||||||
// which isn't possible when searching an arbitrary std::io::Read.
|
// which isn't possible when searching an arbitrary std::io::Read.
|
||||||
if self.multi_line_with_matcher(&matcher) {
|
if self.multi_line_with_matcher(&matcher) {
|
||||||
|
if self.config.multiline_window.is_some() {
|
||||||
|
log::trace!(
|
||||||
|
"{:?}: searching via windowed multiline strategy",
|
||||||
|
path
|
||||||
|
);
|
||||||
|
return self.search_reader(matcher, file, write_to);
|
||||||
|
}
|
||||||
log::trace!(
|
log::trace!(
|
||||||
"{:?}: reading entire file on to heap for mulitline",
|
"{:?}: reading entire file on to heap for mulitline",
|
||||||
path
|
path
|
||||||
@@ -744,6 +763,18 @@ impl Searcher {
|
|||||||
.map_err(S::Error::error_io)?;
|
.map_err(S::Error::error_io)?;
|
||||||
|
|
||||||
if self.multi_line_with_matcher(&matcher) {
|
if self.multi_line_with_matcher(&matcher) {
|
||||||
|
if let Some(window_lines) = self.config.multiline_window {
|
||||||
|
let mut line_buffer = self.line_buffer.borrow_mut();
|
||||||
|
let rdr = LineBufferReader::new(decoder, &mut *line_buffer);
|
||||||
|
log::trace!("generic reader: searching via windowed multiline");
|
||||||
|
return WindowedMultiLine::new(
|
||||||
|
self,
|
||||||
|
matcher,
|
||||||
|
window_lines,
|
||||||
|
write_to,
|
||||||
|
)
|
||||||
|
.run_reader(rdr);
|
||||||
|
}
|
||||||
log::trace!(
|
log::trace!(
|
||||||
"generic reader: reading everything to heap for multiline"
|
"generic reader: reading everything to heap for multiline"
|
||||||
);
|
);
|
||||||
@@ -786,6 +817,16 @@ impl Searcher {
|
|||||||
return self.search_reader(matcher, slice, write_to);
|
return self.search_reader(matcher, slice, write_to);
|
||||||
}
|
}
|
||||||
if self.multi_line_with_matcher(&matcher) {
|
if self.multi_line_with_matcher(&matcher) {
|
||||||
|
if let Some(window_lines) = self.config.multiline_window {
|
||||||
|
log::trace!("slice reader: searching via windowed multiline");
|
||||||
|
return WindowedMultiLine::new(
|
||||||
|
self,
|
||||||
|
matcher,
|
||||||
|
window_lines,
|
||||||
|
write_to,
|
||||||
|
)
|
||||||
|
.run_slice(slice);
|
||||||
|
}
|
||||||
log::trace!("slice reader: searching via multiline strategy");
|
log::trace!("slice reader: searching via multiline strategy");
|
||||||
MultiLine::new(self, matcher, slice, write_to).run()
|
MultiLine::new(self, matcher, slice, write_to).run()
|
||||||
} else {
|
} else {
|
||||||
@@ -865,6 +906,12 @@ impl Searcher {
|
|||||||
self.config.multi_line
|
self.config.multi_line
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the maximum number of lines a multi-line match may span.
|
||||||
|
#[inline]
|
||||||
|
pub fn multiline_window(&self) -> Option<usize> {
|
||||||
|
self.config.multiline_window
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns true if and only if this searcher is configured to stop when it
|
/// Returns true if and only if this searcher is configured to stop when it
|
||||||
/// finds a non-matching line after a matching one.
|
/// finds a non-matching line after a matching one.
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|||||||
@@ -1,23 +1,23 @@
|
|||||||
class RipgrepBin < Formula
|
class RipgrepBin < Formula
|
||||||
version '15.0.0'
|
version '0.1.0'
|
||||||
desc "Recursively search directories for a regex pattern."
|
desc "Recursively search directories for a regex pattern."
|
||||||
homepage "https://github.com/BurntSushi/ripgrep"
|
homepage "https://github.com/BurntSushi/ripgrep"
|
||||||
|
|
||||||
if OS.mac?
|
if OS.mac?
|
||||||
url "https://github.com/BurntSushi/ripgrep/releases/download/#{version}/ripgrep-#{version}-x86_64-apple-darwin.tar.gz"
|
url "https://git.peisongxiao.com/peisongxiao/rgs/releases/download/#{version}/rgs-#{version}-x86_64-apple-darwin.tar.gz"
|
||||||
sha256 "64811cb24e77cac3057d6c40b63ac9becf9082eedd54ca411b475b755d334882"
|
sha256 "64811cb24e77cac3057d6c40b63ac9becf9082eedd54ca411b475b755d334882"
|
||||||
elsif OS.linux?
|
elsif OS.linux?
|
||||||
url "https://github.com/BurntSushi/ripgrep/releases/download/#{version}/ripgrep-#{version}-x86_64-unknown-linux-musl.tar.gz"
|
url "https://git.peisongxiao.com/peisongxiao/rgs/releases/download/#{version}/rgs-#{version}-x86_64-unknown-linux-musl.tar.gz"
|
||||||
sha256 "1c9297be4a084eea7ecaedf93eb03d058d6faae29bbc57ecdaf5063921491599"
|
sha256 "1c9297be4a084eea7ecaedf93eb03d058d6faae29bbc57ecdaf5063921491599"
|
||||||
end
|
end
|
||||||
|
|
||||||
conflicts_with "ripgrep"
|
conflicts_with "ripgrep"
|
||||||
|
|
||||||
def install
|
def install
|
||||||
bin.install "rg"
|
bin.install "rgs"
|
||||||
man1.install "doc/rg.1"
|
man1.install "doc/rgs.1"
|
||||||
|
|
||||||
bash_completion.install "complete/rg.bash"
|
bash_completion.install "complete/rgs.bash"
|
||||||
zsh_completion.install "complete/_rg"
|
zsh_completion.install "complete/_rgs"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user