Skip to content

Commit

Permalink
feat: add search command (#33)
Browse files Browse the repository at this point in the history
Add `gh fzf search` command, which provides live results 
for `gh search`. The [search] command works differently than the 
others. There are two different search modes:

1. **gh:** This is the initial mode and nothing is displayed until you
type a query. The mode uses GitHub's search syntax, which you can read
about in their documentation for [issues & prs], [repos], [commits], and
[code].

2. **fzf:** This is mode used by the rest of the commands, which uses
`fzf` search syntax.

The mode is displayed in the bottom left corner. Start by searching with
GitHub syntax. For example, you may type `author:@me` when using the
[issues & prs] subcommands. When you switch modes from `gh` to `fzf`,
the list remains the same and the GitHub query is saved to state and
cleared from the search bar. You can then use `fzf` syntax to filter the
list further. When you switch modes from `fzf` to `gh`, the GitHub query
is restored and the list is reloaded.

> [!WARNING]
> The `search` command is **experimental**, which means it is subject to
breaking changes without a major version bump. Please report any bugs
you find!

[search]: https://github.com/benelan/gh-fzf#search
[issues & prs]: https://docs.github.com/en/search-github/searching-on-github/searching-issues-and-pull-requests
[repos]: https://docs.github.com/en/search-github/searching-on-github/searching-for-repositories
[commits]: https://docs.github.com/en/search-github/searching-on-github/searching-commits
[code]: https://docs.github.com/en/search-github/searching-on-github/searching-code
  • Loading branch information
benelan authored Feb 17, 2025
1 parent a7a076e commit c39d3ad
Show file tree
Hide file tree
Showing 2 changed files with 254 additions and 19 deletions.
70 changes: 70 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ An fzf wrapper around the GitHub CLI.
- [`milestone`](#milestone)
- [`repo`](#repo)
- [`gist`](#gist)
- [`search`](#search)
- [Configuration](#configuration)
- [Related projects](#related-projects)

Expand Down Expand Up @@ -398,6 +399,75 @@ There are also global keybindings that work on all `gh-fzf` commands:
gh fzf gist --public
```
### `search`
> [!WARNING]
> The `search` command is **experimental**, which means it is subject to
> breaking changes without a major version bump. Please report any bugs you
> find!
The `search` command works differently than the others. There are two different
search modes:
1. **gh:** This is the initial mode and nothing is displayed until you type a
query. The mode uses GitHub's search syntax, which you can read about in
their documentation:
- [`issues` & `prs`](https://docs.github.com/en/search-github/searching-on-github/searching-issues-and-pull-requests)
- [`repos`](https://docs.github.com/en/search-github/searching-on-github/searching-for-repositories)
- [`commits`](https://docs.github.com/en/search-github/searching-on-github/searching-commits)
- [`code`](https://docs.github.com/en/search-github/searching-on-github/searching-code)
2. **fzf:** This is mode used by the rest of the commands, which uses `fzf`
search syntax.
The mode is displayed in the bottom left corner. Start by searching with GitHub
syntax. For example, you may type `author:@me` when using the `issues` or `prs`
subcommands. When you switch modes from `gh` to `fzf`, the list remains the
same and the GitHub query is saved to state and cleared from the search bar.
You can then use `fzf` syntax to filter the list further. When you switch modes
from `fzf` to `gh`, the GitHub query is restored and the list is reloaded.
- **Usage**: `gh fzf search <subcommand> [flags]`
- **Aliases**: `s`, `-s`, `--search`
- **Subcommands:** `issues`, `prs`, `repos`, `commits`, `code`
- **Flags**: See `gh search <subcommand> --help` for available options
- **Keybindings**:
| Key | Description | Configuration Environment Variable |
| ------- | ------------------------------------------ | ---------------------------------- |
| `alt-/` | Toggle between `gh` and `fzf` search modes | `GH_FZF_SEARCH_TOGGLE_MODE_KEY` |
- **Examples:**
- Search for open issues that you're involved with, sorted by most recent update:
```sh
gh fzf search issues state:open involves:@me sort:updated
```
- Search for public repos that use the "lua" language and have the "neovim-plugin" topic, sorted by the number of stars:
```sh
gh fzf search repos --visibility=public --language=lua --topic=neovim-plugin --sort=stars
```
- Search for commits you made in 2024 that contain the phrase "breaking change" in the message:
```sh
gh fzf search commits --author=$(gh api user --jq '.login') --author-date=2024-01-01..2024-12-31 \"breaking change\"
```
- Search for code in repos you own that matches "fetch":
```sh
gh fzf search code --owner=$(gh api user --jq '.login') fetch
```
## Configuration
Environment variables are used to configure `gh-fzf`.
Expand Down
203 changes: 184 additions & 19 deletions gh-fzf
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ set -e

GH_FZF_VERSION="v0.14.1" # x-release-please-version

# USAGE INFO AND LOGS {{{1
FZF_USER_VERSION="$(fzf --version | awk '{print $1}')"

# USAGE INFO AND INTERNAL FUNCTIONS {{{1

has() { command -v "$1" >/dev/null 2>&1; }

Expand Down Expand Up @@ -58,6 +60,12 @@ version_number() {
echo "$@" | awk -F. '{ printf("%d%03d%03d%03d\n", $1,$2,$3,$4); }'
}

requires_fzf_version() {
if [ "$(version_number "$1")" -gt "$(version_number "$FZF_USER_VERSION")" ]; then
error "${2:-"gh-fzf"} requires fzf version >= $1 (found $FZF_USER_VERSION)"
fi
}

error() {
if [ -n "$1" ]; then
printf "Error: " >&2
Expand All @@ -74,7 +82,8 @@ An fzf wrapper around the GitHub CLI.
Homepage: https://github.com/benelan/gh-fzf
Usage:
gh fzf <command> [flags]
gh fzf [<command>] [flags]
gh fzf search [<subcommand>] [flags]
Core Commands:
issue Search for and interact with GitHub issues.
Expand All @@ -86,8 +95,11 @@ Core Commands:
milestone Search for and interact with GitHub milestones via \`gh api\`.
repo Search for and interact with GitHub repos.
gist Search for and interact with GitHub gists.
search Search globally for GitHub issues, prs, commits, and code.
See \`gh <command> list --help\` for the flag options.
All arguments are passed directly to gh. For flag options see:
gh <command> list --help
gh search <subcommand> --help
Other Commands:
help Print this help message.
Expand Down Expand Up @@ -191,7 +203,7 @@ export FZF_DEFAULT_OPTS='

default_cmd() {
FZF_DEFAULT_COMMAND="printf '%s\n' COMMAND \
issue pr run workflow release label milestone repo gist" \
issue pr run workflow release label milestone repo gist search" \
fzf \
--preview='
cmd={};
Expand Down Expand Up @@ -946,6 +958,126 @@ Filters > ($GH_FZF_GIST_PUBLIC_FILTER_KEY: public) ($GH_FZF_GIST_SECRET_FILTER_K
#2}}}
}

# COMMAND > SEARCH {{{1
search_cmd() {
rm -rf ${TMPDIR:-/tmp}/gh-fzf-search-*

if [ -n "$1" ]; then
search_subcmd="$1"
case $search_subcmd in
issues | prs | commits)
search_open_cmd="gh browse --repo {1} {2}"
search_copy_cmd="gh browse --no-browser --repo {1} {2} | $GH_FZF_COPY_CMD"
;;
repos)
search_open_cmd="gh browse --repo {1}"
search_copy_cmd="gh browse --no-browser --repo {1} | $GH_FZF_COPY_CMD"
;;
code)
# NOTE: requires fzf >= v0.53.0 for multiline support
# https://github.com/junegunn/fzf/releases/tag/0.53.0
requires_fzf_version "0.53.0" "code search"

multiline_chunker="| perl -0 -pe 's/^\n/\n\0/gm'"
search_subcmd_opts="--header-lines=2 --read0 --highlight-line"

search_open_cmd="gh browse --repo \$(echo {1})"
search_copy_cmd="gh browse --no-browser --repo \$(echo {1}) | $GH_FZF_COPY_CMD"
;;
*)
error "invalid search subcommand: \"$search_subcmd\"" \
"Valid subcommands: issues, prs, repos, commits, code"
;;
esac
shift
else
FZF_DEFAULT_COMMAND="printf '%s\n' issues prs repos commits code" \
fzf --ansi --header="COMMAND" --header-lines=0 \
--bind="resize:refresh-preview" \
--preview='GH_FORCE_TTY=$FZF_PREVIEW_COLUMNS gh help search {}' \
--preview-window='right,75%,wrap,<75(down,border-top,75%,wrap)' \
--bind="enter:execute(gh fzf search {})"
exit "$?"
fi

echo "$GH_FZF_DEFAULT_LIMIT" >"${TMPDIR:-/tmp}/gh-fzf-search-limit"

# KEYBINDINGS {{{2
export GH_FZF_SEARCH_TOGGLE_MODE_KEY="${GH_FZF_SEARCH_TOGGLE_MODE_KEY:-alt-/}"

search_header="Actions > ($GH_FZF_SEARCH_TOGGLE_MODE_KEY: toggle mode)
$global_binds
"

# FZF COMMAND {{{2
search_cmd="GH_FORCE_TTY=$GH_COLUMNS gh search $search_subcmd -L \$(cat "${TMPDIR:-/tmp}/gh-fzf-search-limit")"

fzf \
--header="$search_header" \
--header-lines=4 \
--disabled \
--query="$*" \
$search_subcmd_opts \
--border="bottom" \
--border-label="[mode: gh] " \
--border-label-pos=-99999 \
--prompt="search $search_subcmd" \
--bind="start:${GH_FZF_HIDE_HINTS:+"toggle-header+"}reload-sync(
$search_cmd \$FZF_QUERY $multiline_chunker || true
)" \
--bind "change:reload-sync:sleep 0.5; $search_cmd \$FZF_QUERY $multiline_chunker || true" \
--bind "$GH_FZF_SEARCH_TOGGLE_MODE_KEY"':transform:[[ ! $FZF_BORDER_LABEL =~ gh ]] &&
echo "change-border-label([mode: gh] )+transform-query(
echo \{q} > ${TMPDIR:-/tmp}/gh-fzf-search-query-fzf;
cat ${TMPDIR:-/tmp}/gh-fzf-search-query-gh
)+rebind(change)+disable-search+reload-sync(
'"$search_cmd"' $FZF_QUERY '"$multiline_chunker"' || true
)" ||
echo "change-border-label([mode: fzf] )+transform-query(
echo \{q} > ${TMPDIR:-/tmp}/gh-fzf-search-query-gh;
cat ${TMPDIR:-/tmp}/gh-fzf-search-query-fzf
)+unbind(change)+enable-search"
' \
--bind="$GH_FZF_RELOAD_KEY:execute-silent(
rm -f \${TMPDIR:-/tmp}/gh-fzf-search-query-{gh,fzf}
)+transform-query(echo \"$*\")+first+reload-sync:$search_cmd \"$*\"" \
--bind="$GH_FZF_OPEN_KEY:execute-silent:$search_open_cmd &" \
--bind="$GH_FZF_COPY_KEY:execute-silent:$search_copy_cmd" \
--bind="$GH_FZF_HELP_KEY:execute(GH_PAGER='less -p \"^ ### .search\"' gh repo view benelan/gh-fzf)" \
--bind="alt-1:execute-silent(
echo 100 > ${TMPDIR:-/tmp}/gh-fzf-search-limit
)+reload-sync($search_cmd \$FZF_QUERY $multiline_chunker || true)" \
--bind="alt-2:execute-silent(
echo 200 > ${TMPDIR:-/tmp}/gh-fzf-search-limit
)+reload-sync($search_cmd \$FZF_QUERY $multiline_chunker || true)" \
--bind="alt-3:execute-silent(
echo 300 > ${TMPDIR:-/tmp}/gh-fzf-search-limit
)+reload-sync($search_cmd \$FZF_QUERY $multiline_chunker || true)" \
--bind="alt-4:execute-silent(
echo 400 > ${TMPDIR:-/tmp}/gh-fzf-search-limit
)+reload-sync($search_cmd \$FZF_QUERY $multiline_chunker || true)" \
--bind="alt-5:execute-silent(
echo 500 > ${TMPDIR:-/tmp}/gh-fzf-search-limit
)+reload-sync($search_cmd \$FZF_QUERY $multiline_chunker || true)" \
--bind="alt-6:execute-silent(
echo 600 > ${TMPDIR:-/tmp}/gh-fzf-search-limit
)+reload-sync($search_cmd \$FZF_QUERY $multiline_chunker || true)" \
--bind="alt-7:execute-silent(
echo 700 > ${TMPDIR:-/tmp}/gh-fzf-search-limit
)+reload-sync($search_cmd \$FZF_QUERY $multiline_chunker || true)" \
--bind="alt-8:execute-silent(
echo 800 > ${TMPDIR:-/tmp}/gh-fzf-search-limit
)+reload-sync($search_cmd \$FZF_QUERY $multiline_chunker || true)" \
--bind="alt-9:execute-silent(
echo 900 > ${TMPDIR:-/tmp}/gh-fzf-search-limit
)+reload-sync($search_cmd \$FZF_QUERY $multiline_chunker || true)" \
</dev/null

#2}}}

rm -f ${TMPDIR:-/tmp}/gh-fzf-search-*
}

# ----------------------------------------------------------------------1}}}
# COMMAND > UTIL {{{1

Expand Down Expand Up @@ -1173,19 +1305,48 @@ main() {
find_repo_flag "$@"

case $command in
h | -h | help | --help) help_cmd "$@" ;;
i | -i | issue | issues | --issue | --issues) issue_cmd "$@" ;;
p | -p | pr | prs | --pr | --prs) pr_cmd "$@" ;;
r | -r | run | runs | --run | --runs) run_cmd "$@" ;;
repo | repos | --repo | --repos) repo_cmd "$@" ;;
release | releases | --release | --releases) release_cmd "$@" ;;
gist | gists | --gist | --gists) gist_cmd "$@" ;;
workflow | workflows | --workflow | --workflows) workflow_cmd "$@" ;;
label | labels | --label | --labels) label_cmd "$@" ;;
milestone | milestones | --milestone | --milestones) milestone_cmd "$@" ;;
util | utils | --util | --utils) util_cmd "$@" ;;
status) echo "$GH_STATUS" ;;
changelog) gh fzf release --repo benelan/gh-fzf ;;
h | -h | help | --help)
help_cmd "$@"
;;
i | -i | issue | issues | --issue | --issues)
issue_cmd "$@"
;;
p | -p | pr | prs | --pr | --prs)
pr_cmd "$@"
;;
s | -s | search | --search)
search_cmd "$@"
;;
r | -r | run | runs | --run | --runs)
run_cmd "$@"
;;
repo | repos | --repo | --repos)
repo_cmd "$@"
;;
release | releases | --release | --releases)
release_cmd "$@"
;;
gist | gists | --gist | --gists)
gist_cmd "$@"
;;
workflow | workflows | --workflow | --workflows)
workflow_cmd "$@"
;;
label | labels | --label | --labels)
label_cmd "$@"
;;
milestone | milestones | --milestone | --milestones)
milestone_cmd "$@"
;;
util | utils | --util | --utils)
util_cmd "$@"
;;
status)
echo "$GH_STATUS"
;;
changelog)
gh fzf release --repo benelan/gh-fzf
;;
upgrade)
latest_version=$(gh release view --json tagName --jq .tagName --repo benelan/gh-fzf)
if [ "$(version_number "$latest_version")" -gt "$(version_number "$GH_FZF_VERSION")" ]; then
Expand All @@ -1195,8 +1356,12 @@ main() {
echo "You are up to date!"
fi
;;
v | V | -v | -V | version | --version) printf "%s\n" "$GH_FZF_VERSION" ;;
*) error "invalid command: \"$command\"" ;;
v | V | -v | -V | version | --version)
printf "%s\n" "$GH_FZF_VERSION"
;;
*)
error "invalid command: \"$command\""
;;
esac
} #2}}}

Expand Down

0 comments on commit c39d3ad

Please sign in to comment.