Merge pull request 'feat(functions): add open-url and repo-open browser helpers' (#67) from feat-open-url-and-repo-open into main
Reviewed-on: #67
This commit was merged in pull request #67.
This commit is contained in:
@@ -134,6 +134,7 @@ To browse the docs from the terminal:
|
||||
| `help config <keyword> --html` | Open HTML docs at the matching section anchor |
|
||||
| `help config --man` | Open the compiled man page via `man -l` |
|
||||
| `help config <keyword> --man` | Open the man page jumping to the nearest match |
|
||||
| `repo-open` | Open this repo's web page in the browser (deep-links to the current branch and sub-directory) |
|
||||
|
||||
The pager falls back through: **ov** → **bat** → **man -l** → **less** → **cat**.
|
||||
|
||||
|
||||
@@ -313,6 +313,12 @@ abbr -a ssct 'sudo systemctl status'
|
||||
abbr -a sscs 'sudo systemctl start'
|
||||
abbr -a sscr 'sudo systemctl restart'
|
||||
|
||||
### Alternate command names ###
|
||||
# Expand to the canonical function name so muscle-memory typos still work,
|
||||
# while surfacing the real command instead of silently forwarding to it.
|
||||
abbr -a open-repo repo-open
|
||||
abbr -a url-open open-url
|
||||
|
||||
### History Expansions and Substitutions ###
|
||||
# Bash-style history expansion is opinionated (C3 overrides), gated atomically
|
||||
# with conf.d/tricks.fish, conf.d/puffer.fish, and functions/expand_*.fish.
|
||||
|
||||
@@ -157,6 +157,10 @@ spark=### spark
|
||||
yt-dlp=### yt-dlp
|
||||
miscfns=## 5.14 Miscellaneous
|
||||
config-help=### config-help
|
||||
open-url=### open-url
|
||||
url-open=### open-url
|
||||
repo-open=### repo-open
|
||||
open-repo=### repo-open
|
||||
config-update=### config-update
|
||||
config-settings=### config-settings
|
||||
config-toggle=### config-settings
|
||||
|
||||
@@ -1339,6 +1339,69 @@ Add -i (interactive confirmation) to destructive commands:
|
||||
|
||||
Also available as: help config [SECTION] [FLAGS]
|
||||
|
||||
### open-url
|
||||
|
||||
Synopsis: open-url [-s|--silent] [-v|--verbose] <url>
|
||||
open-url -h | --help
|
||||
|
||||
Opens a URL or file:// URI in the best available graphical web browser,
|
||||
backgrounded so it never blocks the terminal. Resolves a real browser
|
||||
binary rather than deferring to xdg-open, whose MIME dispatch can hand
|
||||
local text/html files to non-browser apps (e.g. ebook readers).
|
||||
|
||||
Silent by default: prints nothing on success (errors always go to
|
||||
stderr). Pass --verbose / -v to report which browser is launched;
|
||||
--silent / -s is accepted for explicitness.
|
||||
|
||||
Resolution order:
|
||||
1. $fish_help_browser (explicit override)
|
||||
2. $BROWSER (validated; errors if not a command)
|
||||
3. xdg-mime default handler for x-scheme-handler/https
|
||||
4. First known browser binary found in a built-in list
|
||||
5. xdg-open (last resort)
|
||||
|
||||
open-url https://git.rootiest.dev/rootiest/fish-config
|
||||
open-url "file://$HOME/.config/fish/docs/html/index.html"
|
||||
|
||||
Used internally by config-help --html.
|
||||
|
||||
Typo abbreviation: url-open (expands to open-url on space/enter).
|
||||
|
||||
### repo-open
|
||||
|
||||
Synopsis: repo-open [-p|--print] [-r|--root]
|
||||
repo-open -h | --help
|
||||
|
||||
Opens the web page for the current repository's `origin` remote in a
|
||||
browser (via open-url). Deep-links to the current branch when it exists
|
||||
on the remote — falling back to the remote's default branch (main/master)
|
||||
otherwise — and to the current sub-directory when run below the repo root.
|
||||
|
||||
The remote URL is normalized from HTTPS and SSH/scp forms
|
||||
(git@host:owner/repo.git, ssh://…, https://…). The web path layout is
|
||||
provider-specific; the provider is resolved in order:
|
||||
|
||||
1. git config browse.provider (per-repo or --global override)
|
||||
2. Hostname heuristic (github / gitlab / gitea / bitbucket;
|
||||
codeberg → gitea)
|
||||
3. Default: github-style layout
|
||||
|
||||
Self-hosted hosts the heuristic can't classify (a Gitea/GitLab instance
|
||||
on a custom domain) need a one-time override:
|
||||
|
||||
git config browse.provider gitea
|
||||
|
||||
Flags:
|
||||
--print / -p Print the resolved URL instead of opening it.
|
||||
--root / -r Ignore the current sub-directory; link to the repo root.
|
||||
--help / -h Show usage.
|
||||
|
||||
repo-open
|
||||
repo-open --print
|
||||
repo-open --root
|
||||
|
||||
Typo abbreviation: open-repo (expands to repo-open on space/enter).
|
||||
|
||||
### config-update
|
||||
|
||||
Synopsis: config-update [-h] [-n] [-f]
|
||||
|
||||
@@ -138,71 +138,8 @@ function config-help --description 'Open the offline fish shell configuration ma
|
||||
|
||||
set -l page_url "file://$html_dir/$html_path"
|
||||
|
||||
# Browser detection — mirrors fish's help.fish priority order but
|
||||
# resolves actual browser binaries before falling back to xdg-open.
|
||||
# xdg-open dispatches on the file's MIME type (text/html), which can
|
||||
# be associated with non-browser apps (e.g. ebook readers). Using a
|
||||
# real browser binary directly with a file:// URI avoids that lookup.
|
||||
set -l graphical_browsers \
|
||||
firefox firefox-esr chromium chromium-browser google-chrome \
|
||||
brave-browser vivaldi vivaldi-stable epiphany falkon qutebrowser \
|
||||
opera x-www-browser htmlview
|
||||
|
||||
set -l browser $fish_help_browser
|
||||
|
||||
if not set -q browser[1]
|
||||
if set -q BROWSER
|
||||
echo $BROWSER | read -at browser
|
||||
if not type -q $browser[1]
|
||||
set_color red
|
||||
echo "error: \$BROWSER '$browser[1]' is not a valid command" >&2
|
||||
set_color normal
|
||||
return 1
|
||||
end
|
||||
else
|
||||
# Resolve the https scheme handler from xdg-mime and use its
|
||||
# binary directly — most reliable on modern Linux desktops.
|
||||
if type -q xdg-mime
|
||||
set -l desk (xdg-mime query default x-scheme-handler/https 2>/dev/null)
|
||||
if test -n "$desk"
|
||||
set -l candidate (string replace -r '\.desktop$' '' -- $desk)
|
||||
if type -q $candidate
|
||||
set browser $candidate
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Fall back to trying known browser binaries in order.
|
||||
if not set -q browser[1]
|
||||
for b in $graphical_browsers
|
||||
if type -q -f $b
|
||||
set browser $b
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Last resort: xdg-open (may hit wrong app for local files).
|
||||
if not set -q browser[1]; and type -q xdg-open
|
||||
set browser xdg-open
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not set -q browser[1]
|
||||
set_color red
|
||||
echo "error: could not find a web browser — set \$fish_help_browser or \$BROWSER" >&2
|
||||
set_color normal
|
||||
return 1
|
||||
end
|
||||
|
||||
set_color green
|
||||
echo "Opening HTML docs in $browser[1]…"
|
||||
set_color normal
|
||||
|
||||
# Background the browser so it doesn't block the terminal.
|
||||
sh -c '("$@") &' -- $browser $page_url
|
||||
return 0
|
||||
open-url $page_url
|
||||
return $status
|
||||
end
|
||||
|
||||
# ── --man / -m ───────────────────────────────────────────────
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
# Copyright (C) 2026 Rootiest
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
# SYNOPSIS
|
||||
# open-url [-s|--silent] [-v|--verbose] <url>
|
||||
# open-url --help
|
||||
#
|
||||
# DESCRIPTION
|
||||
# Opens a URL (or file:// URI) in the best available graphical web browser,
|
||||
# backgrounded so it never blocks the terminal. Resolves a real browser
|
||||
# binary rather than deferring to xdg-open, whose MIME dispatch can hand
|
||||
# local text/html files to non-browser apps (e.g. ebook readers).
|
||||
#
|
||||
# Silent by default: prints nothing on success (errors always go to stderr).
|
||||
#
|
||||
# Resolution order:
|
||||
# 1. $fish_help_browser (explicit override)
|
||||
# 2. $BROWSER (validated; errors if not a command)
|
||||
# 3. xdg-mime default handler for x-scheme-handler/https
|
||||
# 4. First known browser binary found in a built-in list
|
||||
# 5. xdg-open (last resort)
|
||||
#
|
||||
# ARGUMENTS
|
||||
# url The URL or file:// URI to open (required)
|
||||
# -s, --silent Suppress success output (the default)
|
||||
# -v, --verbose Print which browser is being launched
|
||||
# -h, --help Print usage and exit
|
||||
#
|
||||
# RETURNS
|
||||
# 0 Browser launched
|
||||
# 1 No URL given, invalid $BROWSER, or no browser found
|
||||
#
|
||||
# EXAMPLE
|
||||
# open-url https://git.rootiest.dev/rootiest/fish-config
|
||||
# open-url -v "file://$HOME/.config/fish/docs/html/index.html"
|
||||
function open-url --description 'Open a URL in the best available web browser'
|
||||
argparse h/help s/silent v/verbose -- $argv
|
||||
or return 1
|
||||
|
||||
if set -q _flag_help
|
||||
echo "Usage: open-url [-s|--silent] [-v|--verbose] <url>"
|
||||
echo "Open a URL or file:// URI in the best available web browser."
|
||||
echo
|
||||
echo " -s, --silent Suppress success output (the default)"
|
||||
echo " -v, --verbose Print which browser is being launched"
|
||||
echo " -h, --help Show this help"
|
||||
return 0
|
||||
end
|
||||
|
||||
set -l url $argv[1]
|
||||
if test -z "$url"
|
||||
set_color red
|
||||
echo "error: open-url requires a URL argument" >&2
|
||||
set_color normal
|
||||
return 1
|
||||
end
|
||||
|
||||
# Browser detection — mirrors fish's help.fish priority order but
|
||||
# resolves actual browser binaries before falling back to xdg-open.
|
||||
# xdg-open dispatches on the file's MIME type (text/html), which can
|
||||
# be associated with non-browser apps (e.g. ebook readers). Using a
|
||||
# real browser binary directly with a file:// URI avoids that lookup.
|
||||
set -l graphical_browsers \
|
||||
firefox firefox-esr chromium chromium-browser google-chrome \
|
||||
brave-browser vivaldi vivaldi-stable epiphany falkon qutebrowser \
|
||||
opera x-www-browser htmlview
|
||||
|
||||
set -l browser $fish_help_browser
|
||||
|
||||
if not set -q browser[1]
|
||||
if set -q BROWSER
|
||||
echo $BROWSER | read -at browser
|
||||
if not type -q $browser[1]
|
||||
set_color red
|
||||
echo "error: \$BROWSER '$browser[1]' is not a valid command" >&2
|
||||
set_color normal
|
||||
return 1
|
||||
end
|
||||
else
|
||||
# Resolve the https scheme handler from xdg-mime and use its
|
||||
# binary directly — most reliable on modern Linux desktops.
|
||||
if type -q xdg-mime
|
||||
set -l desk (xdg-mime query default x-scheme-handler/https 2>/dev/null)
|
||||
if test -n "$desk"
|
||||
set -l candidate (string replace -r '\.desktop$' '' -- $desk)
|
||||
if type -q $candidate
|
||||
set browser $candidate
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Fall back to trying known browser binaries in order.
|
||||
if not set -q browser[1]
|
||||
for b in $graphical_browsers
|
||||
if type -q -f $b
|
||||
set browser $b
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Last resort: xdg-open (may hit wrong app for local files).
|
||||
if not set -q browser[1]; and type -q xdg-open
|
||||
set browser xdg-open
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not set -q browser[1]
|
||||
set_color red
|
||||
echo "error: could not find a web browser — set \$fish_help_browser or \$BROWSER" >&2
|
||||
set_color normal
|
||||
return 1
|
||||
end
|
||||
|
||||
if set -q _flag_verbose
|
||||
set_color green
|
||||
echo "Opening $browser[1]…"
|
||||
set_color normal
|
||||
end
|
||||
|
||||
# Background the browser so it doesn't block the terminal, and discard its
|
||||
# own console chatter (e.g. "Opening in existing browser session.").
|
||||
sh -c '("$@") >/dev/null 2>&1 &' -- $browser $url
|
||||
return 0
|
||||
end
|
||||
@@ -0,0 +1,174 @@
|
||||
# Copyright (C) 2026 Rootiest
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
# SYNOPSIS
|
||||
# repo-open [-p|--print] [-r|--root]
|
||||
# repo-open --help
|
||||
#
|
||||
# DESCRIPTION
|
||||
# Opens the web page for the current repository's `origin` remote in a
|
||||
# browser (via open-url). Deep-links to the current branch when it exists
|
||||
# on the remote, falling back to the remote's default branch (main/master)
|
||||
# otherwise, and to the current sub-directory when invoked below the repo
|
||||
# root.
|
||||
#
|
||||
# The remote URL is normalized from both HTTPS and SSH/scp forms
|
||||
# (git@host:owner/repo.git, ssh://…, https://…). The web path layout is
|
||||
# provider-specific; the provider is resolved in this order:
|
||||
#
|
||||
# 1. git config browse.provider (per-repo or --global override)
|
||||
# 2. Hostname heuristic (github / gitlab / gitea / bitbucket;
|
||||
# codeberg → gitea)
|
||||
# 3. Default: github-style layout
|
||||
#
|
||||
# For a self-hosted host the heuristic can't classify (e.g. a Gitea or
|
||||
# GitLab instance on a custom domain), set the provider once:
|
||||
#
|
||||
# git config browse.provider gitea
|
||||
#
|
||||
# ARGUMENTS
|
||||
# -p, --print Print the resolved URL instead of opening it
|
||||
# -r, --root Ignore the current sub-directory; link to the repo root
|
||||
# -h, --help Print usage and exit
|
||||
#
|
||||
# RETURNS
|
||||
# 0 URL opened (or printed)
|
||||
# 1 Not a git repo, no origin remote, or browser launch failed
|
||||
#
|
||||
# EXAMPLE
|
||||
# repo-open # open current branch (+ subdir) in browser
|
||||
# repo-open --print # just print the URL
|
||||
# repo-open --root # repo home page for the current branch
|
||||
function repo-open --description 'Open the origin remote of the current repo in a browser'
|
||||
argparse -X 0 h/help p/print r/root -- $argv
|
||||
or return 1
|
||||
|
||||
if set -q _flag_help
|
||||
echo "Usage: repo-open [-p|--print] [-r|--root]"
|
||||
echo "Open the origin remote's web page for the current repo,"
|
||||
echo "deep-linking to the current branch and sub-directory."
|
||||
echo
|
||||
echo " -p, --print Print the URL instead of opening it"
|
||||
echo " -r, --root Link to the repo root, ignoring the current sub-directory"
|
||||
echo " -h, --help Show this help"
|
||||
return 0
|
||||
end
|
||||
|
||||
if not git rev-parse --is-inside-work-tree >/dev/null 2>&1
|
||||
set_color red
|
||||
echo "error: not inside a git repository" >&2
|
||||
set_color normal
|
||||
return 1
|
||||
end
|
||||
|
||||
set -l remote (git remote get-url origin 2>/dev/null)
|
||||
if test -z "$remote"
|
||||
set_color red
|
||||
echo "error: no 'origin' remote configured" >&2
|
||||
set_color normal
|
||||
return 1
|
||||
end
|
||||
|
||||
# ── Normalize remote → host + owner/repo path ────────────────
|
||||
set -l had_scheme 0
|
||||
if string match -qr '://' -- $remote
|
||||
set had_scheme 1
|
||||
end
|
||||
set -l u (string replace -r '^[a-z0-9]+://' '' -- $remote) # strip scheme
|
||||
set u (string replace -r '^[^@/]+@' '' -- $u) # strip user@
|
||||
set u (string replace -r '\.git$' '' -- $u) # strip .git
|
||||
if test $had_scheme -eq 0
|
||||
set u (string replace ':' '/' -- $u) # scp: first colon → path sep
|
||||
else
|
||||
set u (string replace -r ':[0-9]+/' '/' -- $u) # url: drop :port
|
||||
end
|
||||
|
||||
set -l parts (string split -m1 '/' -- $u)
|
||||
set -l host $parts[1]
|
||||
set -l repo_path $parts[2]
|
||||
if test -z "$host"; or test -z "$repo_path"
|
||||
set_color red
|
||||
echo "error: could not parse origin remote: $remote" >&2
|
||||
set_color normal
|
||||
return 1
|
||||
end
|
||||
set -l base "https://$host/$repo_path"
|
||||
|
||||
# ── Resolve provider (config → hostname → default) ───────────
|
||||
set -l provider (git config --get browse.provider 2>/dev/null)
|
||||
if test -z "$provider"
|
||||
switch $host
|
||||
case 'github.com' 'www.github.com'
|
||||
set provider github
|
||||
case '*gitlab*'
|
||||
set provider gitlab
|
||||
case '*gitea*' 'codeberg.org'
|
||||
set provider gitea
|
||||
case '*bitbucket*'
|
||||
set provider bitbucket
|
||||
case '*'
|
||||
set provider github # sensible default; override via browse.provider
|
||||
end
|
||||
end
|
||||
|
||||
# ── Determine the default branch (for the "on default" check) ─
|
||||
set -l default_branch (git symbolic-ref --quiet --short refs/remotes/origin/HEAD 2>/dev/null \
|
||||
| string replace -r '^origin/' '')
|
||||
if test -z "$default_branch"
|
||||
for b in main master
|
||||
if git rev-parse --verify --quiet refs/remotes/origin/$b >/dev/null 2>&1
|
||||
set default_branch $b
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
test -z "$default_branch"; and set default_branch main
|
||||
|
||||
# ── Determine the branch to link to ──────────────────────────
|
||||
set -l branch (git symbolic-ref --quiet --short HEAD 2>/dev/null)
|
||||
if test -n "$branch"
|
||||
# Prefer the local remote-tracking ref (offline, fast); fall back to a
|
||||
# networked ls-remote before giving up on the current branch.
|
||||
if not git rev-parse --verify --quiet refs/remotes/origin/$branch >/dev/null 2>&1
|
||||
if not git ls-remote --exit-code --heads origin $branch >/dev/null 2>&1
|
||||
set branch $default_branch
|
||||
end
|
||||
end
|
||||
else
|
||||
set branch $default_branch # detached HEAD
|
||||
end
|
||||
|
||||
# ── Sub-directory relative to repo root ──────────────────────
|
||||
set -l prefix ""
|
||||
if not set -q _flag_root
|
||||
set prefix (git rev-parse --show-prefix 2>/dev/null | string trim -r -c /)
|
||||
end
|
||||
|
||||
# ── Assemble the provider-specific URL ───────────────────────
|
||||
set -l seg
|
||||
switch $provider
|
||||
case gitlab
|
||||
set seg "/-/tree/$branch"
|
||||
case gitea
|
||||
set seg "/src/branch/$branch"
|
||||
case bitbucket
|
||||
set seg "/src/$branch"
|
||||
case '*' # github and default
|
||||
set seg "/tree/$branch"
|
||||
end
|
||||
|
||||
set -l url $base
|
||||
if test -n "$prefix"
|
||||
set url "$base$seg/$prefix"
|
||||
else if test "$branch" != "$default_branch"
|
||||
set url "$base$seg"
|
||||
end
|
||||
|
||||
if set -q _flag_print
|
||||
echo $url
|
||||
return 0
|
||||
end
|
||||
|
||||
open-url $url
|
||||
return $status
|
||||
end
|
||||
Reference in New Issue
Block a user