diff --git a/README.md b/README.md index c745936..96eb59e 100644 --- a/README.md +++ b/README.md @@ -328,13 +328,17 @@ A curated offline reference manual is available at `docs/fish-config.md`. It cov |---|---| | `help config` | Open the offline manual in the best available pager | | `help config ` | Jump directly to a section matching the keyword | +| `config-help --html` | Open the pre-built HTML docs in the default browser | +| `config-help --man` | Open the compiled man page via `man -l` | -The viewer falls back through: **ov** (syntax highlight + section navigation) → **bat** (syntax highlight) → **man -l** (pre-compiled man page) → **less** → **cat**. +The pager viewer falls back through: **ov** (syntax highlight + section navigation) → **bat** (syntax highlight) → **man -l** (pre-compiled man page) → **less** → **cat**. Examples: `help config keybindings` · `help config pkg` · `help config fish-deps` · `help config abbreviations` > **Tip:** `help config` is the preferred way to access offline docs — it integrates naturally with fish's built-in `help` command. The underlying `config-help` function is still available directly if needed. +The `--html` flag opens `docs/html/index.html` in your default browser. It detects the correct browser by querying the system's `https://` scheme handler (via `xdg-mime`), falling back through known browser binaries, then `xdg-open` as a last resort. Set `$fish_help_browser` or `$BROWSER` to override. + You can also read the documentation as a standard man page — the symlink and `MANPATH` are set up automatically on shell start: ```fish @@ -345,6 +349,18 @@ The man page is auto-generated from `docs/fish-config.md` by the CI pipeline on > **Note:** `fish-config` (hyphen) is this configuration's man page. `fish_config` (underscore) is fish's built-in browser-based configuration tool — a completely separate command. Don't mix them up. +### Updating the Config + +Pull the latest changes from the upstream repository without needing a configured git remote: + +| Command | Description | +|---|---| +| `config-update` | Fetch and apply the latest commits from upstream | +| `config-update --dry-run` | Preview available changes without applying them | +| `config-update --force` | Stash local changes, pull, then restore the stash | + +The remote URL (`https://git.rootiest.dev/rootiest/fish-config.git`) is hard-coded, so `config-update` works even on a fresh clone with no `origin` set. All git output is suppressed; colored status messages report what changed. After a successful pull, reload the shell with `exec fish`. + ### Dependency Management `fish-deps` is a unified command for checking, installing, and updating all tools this config depends on. diff --git a/docs/fish-config.md b/docs/fish-config.md index 1170a03..497a648 100644 --- a/docs/fish-config.md +++ b/docs/fish-config.md @@ -1022,6 +1022,51 @@ These are implemented as keybinding helpers, but can also be typed: ## 5.14 Miscellaneous +### config-help + + Synopsis: config-help [SECTION] + config-help --html + config-help --man + config-help -h | --help + + Opens the offline fish shell configuration manual. Without flags, opens + the Markdown source in the best available pager (ov > bat > man > less > + cat). If SECTION is given, jumps to the first heading matching that + keyword (case-insensitive; checks fish-config.index aliases first). + + Flags: + --html / -w Open docs/html/index.html in the default browser. + Detects the browser via xdg-mime x-scheme-handler/https, + then known binaries, then xdg-open as last resort. + Respects $fish_help_browser and $BROWSER. + --man / -m Open docs/fish-config.1 via man -l directly. + --help / -h Print usage and navigation key reference. + + config-help keybindings + config-help pkg + config-help --html + config-help --man + +### config-update + + Synopsis: config-update [-h] [-n] [-f] + + Pulls the latest fish configuration from the upstream repository + (https://git.rootiest.dev/rootiest/fish-config.git) into ~/.config/fish. + The remote URL is hard-coded, so this works on fresh clones with no git + remote configured. All git output is suppressed; colored messages report + fetch and merge status. After a successful pull, run `exec fish` to + reload. + + Flags: + --dry-run / -n Fetch and show available commits without applying them. + --force / -f Stash local changes, pull, then restore the stash. + --help / -h Show usage. + + config-update + config-update --dry-run + config-update --force + ### bash Synopsis: bash [args...] @@ -1247,18 +1292,28 @@ navigation. ## As a man page -The symlink and MANPATH are configured automatically on shell start: + config-help --man + +Opens the compiled docs/fish-config.1 directly via man -l, bypassing +the pager fallback chain. The symlink and MANPATH are also configured +automatically on shell start for the standard invocation: man fish-config -Or read the compiled file directly: - - man -l ~/.config/fish/docs/fish-config.1 - NOTE: fish-config (hyphen) is this config's man page. fish_config (underscore) is fish's built-in browser-based configuration tool — a completely separate command. Do not mix them up. +## In the browser (HTML) + + config-help --html + +Opens docs/html/index.html in the default web browser. Browser detection +queries the system's x-scheme-handler/https MIME entry (via xdg-mime) to +find the real browser binary, then falls back through known browser +binaries (firefox, chromium, vivaldi, etc.), and finally xdg-open as a +last resort. Set $fish_help_browser or $BROWSER to override. + ## Jumping to a section help config keybindings diff --git a/functions/config-help.fish b/functions/config-help.fish index 323e890..d242d58 100644 --- a/functions/config-help.fish +++ b/functions/config-help.fish @@ -3,6 +3,8 @@ # SYNOPSIS # config-help [section] +# config-help --html +# config-help --man # config-help --help # # DESCRIPTION @@ -12,24 +14,127 @@ # that matches the keyword. Lookup order: docs/fish-config.index (exact # keyword aliases), then a normalized heading scan as fallback. # When opened with ov a sticky navigation hint is shown at the top of the -# screen. Pass --help or -h to print usage and navigation key reference. +# screen. Pass --html / -w to open the pre-built HTML version in the +# default browser via xdg-open. Pass --man / -m to open the compiled +# man page directly via man -l. Pass --help or -h for usage reference. # # ARGUMENTS -# section Optional keyword to jump to a matching section heading -# --help Print usage and navigation reference, then exit -# -h Alias for --help +# section Optional keyword to jump to a matching section heading +# -w, --html Open the offline HTML docs in the default browser +# -m, --man Open the compiled man page via man -l +# -h, --help Print usage and navigation reference, then exit # # RETURNS # 0 Manual displayed (or --help printed) -# 1 Documentation file not found +# 1 Documentation file not found, or required tool not available # # EXAMPLE # config-help # config-help keybindings # config-help pkg # config-help fish-deps +# config-help --html +# config-help --man # config-help --help function config-help --description 'Open the offline fish shell configuration manual' + # ── --html / -w ────────────────────────────────────────────── + if contains -- --html $argv; or contains -- -w $argv + set -l html_file "$__fish_config_dir/docs/html/index.html" + if not test -f "$html_file" + set_color red + echo "error: HTML docs not found at $html_file" >&2 + set_color normal + return 1 + end + + set -l page_url "file://$html_file" + + # 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 + end + + # ── --man / -m ─────────────────────────────────────────────── + if contains -- --man $argv; or contains -- -m $argv + set -l man_file "$__fish_config_dir/docs/fish-config.1" + if not test -f "$man_file" + set_color red + echo "error: man page not found at $man_file" >&2 + set_color normal + return 1 + end + if not type -q man + set_color red + echo "error: man not found — cannot open man page" >&2 + set_color normal + return 1 + end + man -l "$man_file" + return 0 + end + # ── --help / -h ────────────────────────────────────────────── if contains -- --help $argv; or contains -- -h $argv set_color --bold @@ -41,15 +146,19 @@ function config-help --description 'Open the offline fish shell configuration ma echo USAGE set_color normal echo " config-help "(set_color yellow)"[section]"(set_color normal) + echo " config-help "(set_color yellow)"--html"(set_color normal) + echo " config-help "(set_color yellow)"--man"(set_color normal) echo " config-help "(set_color yellow)"--help"(set_color normal) echo "" set_color --bold brblue echo ARGUMENTS set_color normal - echo " "(set_color yellow)"section"(set_color normal)" Optional keyword to jump to a matching section heading." - echo " Searches docs/fish-config.index for aliases first, then" - echo " falls back to a normalized (case- and punctuation-insensitive)" - echo " scan of heading lines." + echo " "(set_color yellow)"section"(set_color normal)" Optional keyword to jump to a matching section heading." + echo " Searches docs/fish-config.index for aliases first, then" + echo " falls back to a normalized (case- and punctuation-insensitive)" + echo " scan of heading lines." + echo " "(set_color yellow)"-w, --html"(set_color normal)" Open the offline HTML docs in the default browser." + echo " "(set_color yellow)"-m, --man"(set_color normal)" Open the compiled man page via man -l." echo "" set_color --bold brblue echo EXAMPLES @@ -59,6 +168,8 @@ function config-help --description 'Open the offline fish shell configuration ma echo " "(set_color green)"config-help pkg"(set_color normal)" jump to the pkg function entry" echo " "(set_color green)"config-help fish-deps"(set_color normal)" jump to fish-deps" echo " "(set_color green)"config-help abbreviations"(set_color normal)" jump to Abbreviations section" + echo " "(set_color green)"config-help --html"(set_color normal)" open HTML docs in browser" + echo " "(set_color green)"config-help --man"(set_color normal)" open compiled man page" echo "" set_color --bold brblue echo "NAVIGATION (ov pager)" diff --git a/functions/config-update.fish b/functions/config-update.fish new file mode 100644 index 0000000..e175228 --- /dev/null +++ b/functions/config-update.fish @@ -0,0 +1,170 @@ +# Copyright (C) 2026 Rootiest +# SPDX-License-Identifier: AGPL-3.0-or-later + +# SYNOPSIS +# config-update [-h | --help] [-f | --force] [-n | --dry-run] +# +# DESCRIPTION +# Pulls the latest fish shell configuration from the upstream repository +# (https://git.rootiest.dev/rootiest/fish-config.git) into ~/.config/fish. +# The remote URL is hard-coded so the update works even if the local clone +# has no configured remote. Git output is suppressed; status is reported +# through colored messages. After a successful pull the function prints a +# short summary of changed files. +# +# ARGUMENTS +# -h, --help Show this help message and exit +# -f, --force Stash local changes before pulling, then pop the stash +# -n, --dry-run Check for upstream changes without applying them +# +# RETURNS +# 0 Config updated (or already up to date) +# 1 Update failed (network error, merge conflict, or not a git repo) +# +# EXAMPLE +# config-update +# config-update --dry-run +# config-update --force +function config-update --description 'Pull latest fish config from upstream' + set -l c_head (set_color --bold cyan) + set -l c_cmd (set_color --bold white) + set -l c_flag (set_color yellow) + set -l c_ok (set_color green) + set -l c_warn (set_color yellow) + set -l c_err (set_color red) + set -l c_dim (set_color brblack) + set -l c_reset (set_color normal) + + set -l REMOTE_URL https://git.rootiest.dev/rootiest/fish-config.git + set -l CONFIG_DIR ~/.config/fish + set -l opt_force 0 + set -l opt_dry 0 + + # ── parse flags ─────────────────────────────────────────────── + for arg in $argv + switch $arg + case -h --help + echo "$c_head""Usage:$c_reset $c_cmd""config-update$c_reset $c_flag""[-h] [-f] [-n]$c_reset" + echo + echo " Pull the latest fish config from upstream and apply it locally." + echo + echo "$c_head""Options:$c_reset" + echo " $c_flag-h$c_reset, $c_flag--help$c_reset Show this help message" + echo " $c_flag-f$c_reset, $c_flag--force$c_reset Stash local changes, pull, then restore stash" + echo " $c_flag-n$c_reset, $c_flag--dry-run$c_reset Check for updates without applying them" + echo + echo "$c_head""Remote:$c_reset $c_dim""$REMOTE_URL$c_reset" + return 0 + case -f --force + set opt_force 1 + case -n --dry-run + set opt_dry 1 + case '*' + echo "$c_err""Unknown option: $arg$c_reset" >&2 + echo "Run $c_cmd""config-update --help$c_reset for usage." >&2 + return 1 + end + end + + # ── verify config dir is a git repo ────────────────────────── + if not test -d "$CONFIG_DIR/.git" + echo "$c_err""Error:$c_reset $c_dim""$CONFIG_DIR$c_reset is not a git repository." >&2 + return 1 + end + + echo "$c_head""Fish Config Updater$c_reset" + echo "$c_dim""Remote: $REMOTE_URL$c_reset" + echo + + # ── fetch upstream ──────────────────────────────────────────── + echo "$c_dim""→ Fetching upstream changes...$c_reset" + if not git -C "$CONFIG_DIR" fetch "$REMOTE_URL" main:refs/remotes/_config_update/main 2>/dev/null + echo "$c_err""Error:$c_reset Could not reach upstream remote." >&2 + echo "$c_dim""Check your internet connection and try again.$c_reset" >&2 + git -C "$CONFIG_DIR" branch -D -r _config_update/main 2>/dev/null 1>/dev/null + return 1 + end + + # ── compare local HEAD to upstream ─────────────────────────── + set -l local_sha (git -C "$CONFIG_DIR" rev-parse HEAD 2>/dev/null) + set -l remote_sha (git -C "$CONFIG_DIR" rev-parse _config_update/main 2>/dev/null) + + if test "$local_sha" = "$remote_sha" + echo "$c_ok""✓ Already up to date.$c_reset" + git -C "$CONFIG_DIR" branch -D -r _config_update/main 2>/dev/null 1>/dev/null + return 0 + end + + # ── count incoming commits ──────────────────────────────────── + set -l ahead (git -C "$CONFIG_DIR" rev-list HEAD.._config_update/main 2>/dev/null | count) + echo "$c_ok""$ahead new commit(s) available upstream.$c_reset" + + # ── list changed files (preview) ───────────────────────────── + set -l changed_files (git -C "$CONFIG_DIR" diff --name-only HEAD _config_update/main 2>/dev/null) + if test (count $changed_files) -gt 0 + echo + echo "$c_head""Changed files:$c_reset" + for f in $changed_files + echo " $c_dim""·$c_reset $f" + end + end + + # ── dry-run exits here ──────────────────────────────────────── + if test $opt_dry -eq 1 + echo + echo "$c_warn""Dry run — no changes applied.$c_reset" + git -C "$CONFIG_DIR" branch -D -r _config_update/main 2>/dev/null 1>/dev/null + return 0 + end + + echo + + # ── check for local modifications ───────────────────────────── + set -l dirty (git -C "$CONFIG_DIR" status --porcelain 2>/dev/null) + if test -n "$dirty" + if test $opt_force -eq 1 + echo "$c_warn""→ Stashing local changes...$c_reset" + if not git -C "$CONFIG_DIR" stash push -m "config-update auto-stash" 2>/dev/null 1>/dev/null + echo "$c_err""Error:$c_reset Could not stash local changes." >&2 + git -C "$CONFIG_DIR" branch -D -r _config_update/main 2>/dev/null 1>/dev/null + return 1 + end + else + echo "$c_warn""Warning:$c_reset You have uncommitted local changes." >&2 + echo "$c_dim""Run with $c_flag--force$c_dim to stash them automatically,$c_reset" >&2 + echo "$c_dim""or commit/stash manually before updating.$c_reset" >&2 + git -C "$CONFIG_DIR" branch -D -r _config_update/main 2>/dev/null 1>/dev/null + return 1 + end + end + + # ── apply the update ────────────────────────────────────────── + echo "$c_dim""→ Applying update...$c_reset" + if not git -C "$CONFIG_DIR" merge --ff-only _config_update/main 2>/dev/null 1>/dev/null + # ff-only failed — try a regular merge + set -l merge_out (git -C "$CONFIG_DIR" merge _config_update/main 2>&1) + if test $status -ne 0 + echo "$c_err""Error:$c_reset Merge failed — resolve conflicts manually." >&2 + echo "$c_dim""$merge_out$c_reset" >&2 + git -C "$CONFIG_DIR" branch -D -r _config_update/main 2>/dev/null 1>/dev/null + return 1 + end + end + + # ── restore stash if we created one ────────────────────────── + if test $opt_force -eq 1 -a -n "$dirty" + echo "$c_dim""→ Restoring stashed changes...$c_reset" + if not git -C "$CONFIG_DIR" stash pop 2>/dev/null 1>/dev/null + echo "$c_warn""Warning:$c_reset Stash pop had conflicts — resolve manually with $c_cmd""git stash pop$c_reset" + end + end + + # ── success summary ─────────────────────────────────────────── + set -l new_sha (git -C "$CONFIG_DIR" rev-parse --short HEAD 2>/dev/null) + echo "$c_ok""✓ Config updated to $new_sha ($ahead commit(s) applied).$c_reset" + echo + echo "$c_dim""Reload your shell or run $c_cmd""exec fish$c_dim to apply changes.$c_reset" + + git -C "$CONFIG_DIR" branch -D -r _config_update/main 2>/dev/null 1>/dev/null + return 0 +end