Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# AGENTS

## Build and test

- `make build` compiles `./build/vgrep`
- `make check` runs golangci-lint
- `go test ./...` runs Go unit tests
- `make container-test` builds and runs all tests (Go + bats integration) inside a container; always use this on macOS as bats tests are very slow natively
- `make test` runs bats integration tests locally (requires `bats`, `less`, `ripgrep`)

## Project structure

- `vgrep.go` is the single main source file containing CLI args, grep dispatch, command parsing and all interactive shell commands
- `internal/ansi/` handles ANSI color formatting with a global disable switch
- `internal/colwriter/` prints columnar output using the ansi package
- Dependencies are vendored in `vendor/`; run `make vendor` after changing `go.mod`

## Conventions

- Use `go build -mod=vendor` (the Makefile handles this)
- Do not modify vendored code directly
- Integration tests are bats scripts in `test/`; follow existing patterns in `test/simple.bats`
- CLI flags are defined as struct tags on `cliArgs` in `vgrep.go` using `github.com/jessevdk/go-flags`

## Commits

- Keep commits small and incremental; one logical change per commit
- Always use the `-s` flag to sign off commits (`git commit -s`)
60 changes: 37 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,32 @@
![vgrep logo](logo.png)

[![Build Status](https://api.cirrus-ci.com/github/vrothberg/vgrep.svg)](https://cirrus-ci.com/github/vrothberg/vgrep)

**vgrep** is a pager for `grep`, `git-grep`, `ripgrep` and similar grep implementations, and allows for opening the indexed file locations in a user-specified editor such as vim or emacs. vgrep is inspired by the ancient **cgvg** scripts but extended to perform further operations such as listing statistics of files and directory trees or showing the context lines before and after the matches. vgrep runs on Linux, Windows and Mac OS.
**vgrep** is a pager for `grep`, `git-grep`, `ripgrep` and similar grep implementations, and allows for opening the indexed file locations in a user-specified editor such as vim or emacs. vgrep is inspired by the ancient **cgvg** scripts but extended to perform further operations such as listing statistics of files and directory trees or showing the context lines before and after the matches. vgrep runs on Linux, Windows and macOS.

Please, feel free to copy, improve, distribute and share. Feedback and patches are always welcome!

# Install Instructions
# Installation Instructions

* You can install `vgrep` on [Fedora](https://src.fedoraproject.org/rpms/vgrep), [openSUSE](https://software.opensuse.org/package/vgrep), [archlinux](https://aur.archlinux.org/packages/vgrep/), [Gentoo](https://packages.gentoo.org/packages/app-text/vgrep), and on Mac OS via [MacPorts](https://ports.macports.org/port/vgrep/summary) and [Homebrew](https://formulae.brew.sh/formula/vgrep).
* You can install `vgrep` on [Fedora](https://src.fedoraproject.org/rpms/vgrep), [openSUSE](https://software.opensuse.org/package/vgrep), [archlinux](https://aur.archlinux.org/packages/vgrep/), [Gentoo](https://packages.gentoo.org/packages/app-text/vgrep), and on macOS via [MacPorts](https://ports.macports.org/port/vgrep/summary) and [Homebrew](https://formulae.brew.sh/formula/vgrep).
* On other systems, you can build and install `vgrep` manually via `make build` and `make install`.

# Options

| Flag | Description |
|------|-------------|
| `-s, --show SELECTORS` | Show specified matches or open the interactive shell |
| `-l, --files-with-matches` | Print matching files only |
| `--interactive` | Enter the interactive shell |
| `--no-header` | Do not print column headers |
| `--no-less` | Write to stdout instead of `less` |
| `--no-git` | Use grep instead of git-grep |
| `--no-ripgrep` | Do not use ripgrep |

All other flags and arguments are passed down to the underlying grep tool. `vgrep` uses the first available search tool in this order: `ripgrep`, `git-grep` (when inside a git tree), `grep`.

`vgrep` respects the `NO_COLOR` environment variable to suppress all ANSI color output (see https://no-color.org/).

# Searching Patterns
The basic functionality of vgrep is to perform textual searches. On a technical level, vgrep serves as a front-end to grep or git-grep when invoking vgrep inside a git tree and uses `less` for displaying the results. All non-vgrep flags and arguments will be passed down to grep. Results of the last search are cached, so running vgrep without a new query will load previous results and operate on them.
The basic functionality of vgrep is to perform textual searches. On a technical level, vgrep serves as a front-end to grep or git-grep when invoking vgrep inside a git tree and uses `less` for displaying the results. Results of the last search are cached, so running vgrep without a new query will load previous results and operate on them.

An example call may look as follows:

Expand All @@ -24,61 +38,61 @@ By default, the output will be written to `less` to make browsing large amounts
vgrep can open the indexed file locations in an editor specified by the `EDITOR` environment variable. Opening one of the file locations from the previous example may look as follows:

```
# export EDITOR=gedit
# vgrep --show 4
$ export EDITOR=gedit
$ vgrep --show 4
```

![](screenshots/vgrep-show-gedit.png)

The default editor of vgrep is `vim` with the default flag to open a file at a specific line being `+` followed by the line number. If your editor of choice hits the rare case of a different syntax, use the `EDITORLINEFLAG` environment variable to adjust. For example, a `kate` user may set the environment to ``EDITOR="kate"`` and ``EDITORLINEFLAG="-l"``.

Note that `vgrep` does not allow for searching and opening files at the same time. For instance, `vgrep --show=files text` should be split in two commands: `vgrep text` and `vgrep --show=files`.
Note that `vgrep` does not allow for searching and opening files at the same time. For instance, `vgrep --show=files text` should be split into two commands: `vgrep text` and `vgrep --show=files`.

## IDE Support

Note that if you run `vgrep` inside a terminal of VSCode or Goland, the format of listed files changes to "$PATH:$LINE" to allow for opening the matches in the editor via a simple mouse click.

# More Commands and the Interactive Shell

Once vgreped, you can perform certain operations on the results such as limiting the range of indexed matches, listing matching files and directories and more.
Once searched, you can perform certain operations on the results such as limiting the range of indexed matches, listing matching files and directories and more.
```
Enter a vgrep command: ?
vgrep command help: command[context lines] [selectors]
selectors: '3' (single), '1,2,6' (multi), '1-8' (range), 'all'
commands: print, show, context, tree, delete, keep, refine, files, grep, quit, ?
```
vgrep commands can be passed directly to the ``--show/-s`` flag, for instance as ``--show c5 1-10`` to show the five context lines of the first ten matched lines. Furthermore, the commands can be executed in an interactive shell via the ``--interactive/-i`` flag. Running ``vgrep --interactive`` will enter the shell directly, ``vgrep --show 1 --interactive`` will first open the first matched line in the editor and enter the interactive shell after.
vgrep commands can be passed directly to the ``--show/-s`` flag, for instance as ``--show c5 1-10`` to show the five context lines of the first ten matched lines. Furthermore, the commands can be executed in an interactive shell via the ``--interactive/-i`` flag. Running ``vgrep --interactive`` will enter the shell directly, ``vgrep --show 1 --interactive`` will first open the first matched line in the editor and enter the interactive shell afterward.

vgrep supports the following commands:

- ``print`` to limit the range of matched lines to be printed. ``p 1-12,20`` prints the first 12 lines and the 20th line.
- ``show`` to open the selectors in an user-specified editor (requires selectors).
- ``show`` to open the selectors in a user-specified editor (requires selectors).
- ``context`` to print the context lines before and after the matched lines. ``c10 3-9`` prints 10 context lines of the matching lines 3 to 9. Unless specified, vgrep will print 5 context lines.
- ``tree`` to print the number of matches for each directory in the tree.
- ``delete`` to remove lines at selected indices from the results, for the duration of the interactive shell (requires selectors).
- ``keep`` to keep only lines at selected indices from the results, for the duration of the interactive shell (requires selectors).
- ``refine`` to keep only lines matching the provided regexp pattern from the results, for the duration of the interactive shell (requires a regexp string).
- ``files`` will print the number of matches for each file in the tree.
- ``grep`` start a new search without leaving the interactive shell (requires arguments for a ``vgrep`` search). For example, ``g -i "foo bar" dir/`` will trigger a case-insensitive search for ``foo bar`` in the files under ``dir``. The cache will be updated with the results from the new search.
- ``files`` to print the number of matches for each file in the tree.
- ``grep`` to start a new search without leaving the interactive shell (requires arguments for a ``vgrep`` search). For example, ``g -i "foo bar" dir/`` will trigger a case-insensitive search for ``foo bar`` in the files under ``dir``. The cache will be updated with the results from the new search.
- ``quit`` to exit the interactive shell.
- ``?`` to show the help for vgrep commands.

# vgrep command examples
## Examples

## Context lines
### Context lines
![](screenshots/vgrep-context.png)

## Tree
### Tree
![](screenshots/vgrep-tree.png)

## Files
### Files
![](screenshots/vgrep-files.png)

## fzf
# fzf Integration

![](https://user-images.githubusercontent.com/7258858/103111382-b00c7f80-464c-11eb-9e47-c36ed89253a1.png)

If you desire a more interactive experience than running vgrep twice to first search and then to open an editor, you may have a look at fzf. The below function uses [fzf](https://github.com/junegunn/fzf) to interactively search with vgrep and open your editor with one enter at the correct line.
If you desire a more interactive experience than running vgrep twice to first search and then to open an editor, you may have a look at fzf. The below function uses [fzf](https://github.com/junegunn/fzf) to interactively search with vgrep and open your editor at the correct line with a single key press.
To use it add the following function to your ``.bashrc`` and install fzf alongside vgrep and ripgrep.

```shell
Expand All @@ -91,9 +105,9 @@ vgrep() {
}
```

### For fish shell
## For fish shell

The below version pipes the result of the initial query with `vgrep` to `fzf` to allow further selection among the results.
The below version pipes the result of the initial query with `vgrep` to `fzf` to allow further selection among the results.

```fish
function vgf --wraps=vgrep --description 'vgrep search with fzf'
Expand All @@ -111,4 +125,4 @@ function vgF --wraps=vgrep --description 'vgrep search with fzf'
end
```

For further details on use of `functions` with `fish` shell, see [here](https://fishshell.com/docs/current/language.html#syntax-function).
For further details on use of `functions` with `fish` shell, see [here](https://fishshell.com/docs/current/language.html#syntax-function).
9 changes: 6 additions & 3 deletions internal/colwriter/colwriter.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,14 @@ func (cw *ColWriter) Open() {
cw.cmd.Stderr = os.Stderr
cw.pipe, err = cw.cmd.StdinPipe()
if err != nil {
panic(fmt.Sprintf("Could not execute less:%s\n", err))
fmt.Fprintf(os.Stderr, "could not execute less: %s\n", err)
os.Exit(1)
}
cw.writer = bufio.NewWriter(cw.pipe)
err = cw.cmd.Start()
if err != nil {
panic(fmt.Sprintf("Could not execute less:%s\n", err))
fmt.Fprintf(os.Stderr, "could not execute less: %s\n", err)
os.Exit(1)
}
}
cw.opened = true
Expand All @@ -104,7 +106,8 @@ func (cw *ColWriter) Close() {
_ = cw.pipe.Close()
err := cw.cmd.Wait()
if err != nil {
panic(fmt.Sprintf("Could not execute less:%s\n", err))
fmt.Fprintf(os.Stderr, "could not execute less: %s\n", err)
os.Exit(1)
}
}
cw.opened = false
Expand Down
6 changes: 6 additions & 0 deletions test/interactive.bats
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ EOF
[[ ${lines[2]} =~ "six" ]]
}

@test "Interactive mode exits cleanly on EOF (Ctrl+D)" {
run_vgrep peanut $FILE > /dev/null
run_vgrep --interactive < /dev/null
[ "$status" -eq 0 ]
}

@test "Interactive mode and new grep search" {
run_vgrep my_pattern > /dev/null
run_vgrep --show "g -w peanut $FILE" \
Expand Down
11 changes: 8 additions & 3 deletions vgrep.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ package main
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"os"
"os/exec"
"os/signal"
Expand Down Expand Up @@ -553,7 +555,7 @@ func (v *vgrep) cacheWriterHelper() error {
}
defer func() {
if err := v.lock.Unlock(); err != nil {
panic(fmt.Sprintf("Error releasing lock file: %v", err))
fmt.Fprintf(os.Stderr, "error releasing lock file: %v\n", err)
}
}()

Expand Down Expand Up @@ -604,7 +606,7 @@ func (v *vgrep) loadCache() error {
}
defer func() {
if err := v.lock.Unlock(); err != nil {
panic(fmt.Sprintf("Error releasing lock file: %v", err))
fmt.Fprintf(os.Stderr, "error releasing lock file: %v\n", err)
}
}()

Expand Down Expand Up @@ -693,8 +695,11 @@ func (v *vgrep) commandParse() {
nextInput := func() string {
usrInp, err := line.Prompt("Enter a vgrep command: ")
if err != nil {
// Either we hit an error or EOF (ctrl+d)
_ = line.Close()
if errors.Is(err, io.EOF) || errors.Is(err, liner.ErrPromptAborted) {
fmt.Println()
os.Exit(0)
}
fmt.Fprintf(os.Stderr, "error parsing user input: %v\n", err)
os.Exit(1)
}
Expand Down
Loading