diff --git a/scripts/core/alias.fish b/scripts/core/alias.fish new file mode 100644 index 0000000..3511e22 --- /dev/null +++ b/scripts/core/alias.fish @@ -0,0 +1,382 @@ +function _clash_fish_project_dir + if set -q CLASH_FOR_LINUX_PROJECT_DIR[1] + echo $CLASH_FOR_LINUX_PROJECT_DIR + return 0 + end + + set -l self (status --current-filename) + if test -n "$self"; and test "$self" != "Standard input" + set -l resolved (realpath "$self" 2>/dev/null) + if test -z "$resolved" + set resolved "$self" + end + dirname (dirname (dirname "$resolved")) + return 0 + end + + pwd +end + +function _clashctl_real + if command -q clashctl-bin + clashctl-bin $argv + return $status + end + + command clashctl $argv +end + +function _clashctl_real_on + set -l project_dir (_clash_fish_project_dir) + set -l clashctl_script "$project_dir/scripts/core/clashctl.sh" + + if test -f "$clashctl_script" + bash "$clashctl_script" on $argv + return $status + end + + _clashctl_real on $argv +end + +function _clashctl_real_off + set -l project_dir (_clash_fish_project_dir) + set -l clashctl_script "$project_dir/scripts/core/clashctl.sh" + + if test -f "$clashctl_script" + bash "$clashctl_script" off $argv + return $status + end + + _clashctl_real off $argv +end + +function _clashctl_real_on_target + set -l project_dir (_clash_fish_project_dir) + set -l clashctl_script "$project_dir/scripts/core/clashctl.sh" + + if test -f "$clashctl_script" + echo "bash $clashctl_script on" + else + echo "_clashctl_real on" + end +end + +function _clash_fish_state_file + set -l project_dir (_clash_fish_project_dir) + echo "$project_dir/runtime/shell-proxy.env" +end + +function _clash_fish_runtime_config_file + set -l project_dir (_clash_fish_project_dir) + echo "$project_dir/runtime/config.yaml" +end + +function _clash_fish_yq_bin + set -l project_dir (_clash_fish_project_dir) + echo "$project_dir/runtime/bin/yq" +end + +function _clash_fish_set_persist_enabled + set -l enabled "$argv[1]" + set -l state_file (_clash_fish_state_file) + set -l now (date '+%Y-%m-%d %H:%M:%S' 2>/dev/null) + + mkdir -p (dirname "$state_file") + printf 'SHELL_PROXY_PERSIST_ENABLED="%s"\nSHELL_PROXY_PERSIST_TIME="%s"\n' \ + "$enabled" "$now" >"$state_file" +end + +function _clash_fish_persist_enabled + set -l state_file (_clash_fish_state_file) + test -f "$state_file"; or return 1 + + set -l enabled (sed -nE 's/^SHELL_PROXY_PERSIST_ENABLED="?([^"\r\n]+)"?$/\1/p' "$state_file" | head -n 1) + test "$enabled" = "true" +end + +function _clash_fish_system_proxy_file + if set -q SYSTEM_PROXY_ENV_FILE[1] + echo $SYSTEM_PROXY_ENV_FILE + return 0 + end + + set -l project_dir (_clash_fish_project_dir) + set -l env_file "$project_dir/.env" + if test -f "$env_file" + set -l value (sed -nE 's/^[[:space:]]*(export[[:space:]]+)?SYSTEM_PROXY_ENV_FILE="?([^"#]+)"?[[:space:]]*$/\2/p' "$env_file" | tail -n 1) + if test -n "$value" + set value (string replace -a '$PROJECT_DIR' "$project_dir" "$value") + set value (string replace -a '${PROJECT_DIR}' "$project_dir" "$value") + echo "$value" + return 0 + end + end + + echo /etc/environment +end + +function _clash_fish_export_proxy_values + set -l http_url "$argv[1]" + set -l https_url "$argv[2]" + set -l all_url "$argv[3]" + set -l no_proxy_value "$argv[4]" + + set -gx http_proxy "$http_url" + set -gx https_proxy "$https_url" + set -gx HTTP_PROXY "$http_url" + set -gx HTTPS_PROXY "$https_url" + set -gx all_proxy "$all_url" + set -gx ALL_PROXY "$all_url" + set -gx no_proxy "$no_proxy_value" + set -gx NO_PROXY "$no_proxy_value" +end + +function _clash_fish_export_system_proxy + set -l proxy_file (_clash_fish_system_proxy_file) + test -f "$proxy_file"; or return 1 + grep -Fq "# >>> clash-for-linux system proxy >>>" "$proxy_file" 2>/dev/null; or return 1 + + set -l http_url (sed -nE 's/^http_proxy="?([^"\r\n]+)"?$/\1/p' "$proxy_file" | tail -n 1) + set -l https_url (sed -nE 's/^https_proxy="?([^"\r\n]+)"?$/\1/p' "$proxy_file" | tail -n 1) + set -l all_url (sed -nE 's/^all_proxy="?([^"\r\n]+)"?$/\1/p' "$proxy_file" | tail -n 1) + set -l no_proxy_value (sed -nE 's/^NO_PROXY="?([^"\r\n]+)"?$/\1/p' "$proxy_file" | tail -n 1) + if test -z "$no_proxy_value" + set no_proxy_value (sed -nE 's/^no_proxy="?([^"\r\n]+)"?$/\1/p' "$proxy_file" | tail -n 1) + end + + test -n "$http_url"; or return 1 + if test -z "$https_url" + set https_url "$http_url" + end + if test -z "$all_url" + set all_url (string replace 'http://' 'socks5://' "$http_url") + end + if test -z "$no_proxy_value" + set no_proxy_value "127.0.0.1,localhost,::1" + end + + _clash_fish_export_proxy_values "$http_url" "$https_url" "$all_url" "$no_proxy_value" +end + +function _clash_fish_runtime_proxy_port + set -l config_file (_clash_fish_runtime_config_file) + test -s "$config_file"; or return 1 + + set -l yq_bin (_clash_fish_yq_bin) + set -l port + if test -x "$yq_bin" + set port ($yq_bin eval '.["mixed-port"] // .port // ""' "$config_file" 2>/dev/null | head -n 1) + else + set port (sed -nE 's/^[[:space:]]*(mixed-port|port):[[:space:]]*"?([0-9]+)"?[[:space:]]*$/\2/p' "$config_file" | head -n 1) + end + + test -n "$port"; and test "$port" != "null"; or return 1 + echo "$port" +end + +function _clash_fish_export_runtime_proxy + set -l port (_clash_fish_runtime_proxy_port); or return $status + set -l host 127.0.0.1 + if set -q CLASH_PROXY_HOST[1] + set host $CLASH_PROXY_HOST + end + set -l no_proxy_value 127.0.0.1,localhost,::1 + if set -q NO_PROXY_DEFAULT[1] + set no_proxy_value $NO_PROXY_DEFAULT + end + + _clash_fish_export_proxy_values "http://$host:$port" "http://$host:$port" "socks5://$host:$port" "$no_proxy_value" +end + +function _clash_fish_export_proxy + _clash_fish_export_system_proxy; or _clash_fish_export_runtime_proxy +end + +function _clash_fish_unset_shell_proxy + set -e http_proxy + set -e https_proxy + set -e HTTP_PROXY + set -e HTTPS_PROXY + set -e all_proxy + set -e ALL_PROXY + set -e no_proxy + set -e NO_PROXY +end + +function _clash_fish_after_on + _clash_fish_set_persist_enabled true + _clash_fish_export_proxy +end + +function _clash_fish_run_on + set -l on_output (mktemp "$TMPDIR/clashon.XXXXXX" 2>/dev/null) + if test -z "$on_output" + set on_output (mktemp "/tmp/clashon.XXXXXX" 2>/dev/null) + end + if test -z "$on_output" + echo "❗ 开启代理失败:无法创建临时输出文件" >&2 + return 1 + end + + set -l on_rc + begin + set -lx CLASH_ALIAS_CALL 1 + _clashctl_real_on $argv >"$on_output" 2>&1 + set on_rc $status + end + + if test "$on_rc" -ne 0 + if _clash_fish_export_system_proxy + echo "🚨 clashctl on 返回非 0,但系统代理已写入,继续同步当前 Shell(底层返回码:$on_rc)" >&2 + if test -s "$on_output" + sed 's/^/ /' "$on_output" >&2 + end + else + echo "❗ 开启代理失败(底层返回码:$on_rc)" >&2 + if test -s "$on_output" + sed 's/^/ /' "$on_output" >&2 + else + echo " 底层命令没有输出错误详情:"(_clashctl_real_on_target) >&2 + end + rm -f "$on_output" 2>/dev/null + return "$on_rc" + end + else + cat "$on_output" + end + + rm -f "$on_output" 2>/dev/null + + _clash_fish_after_on + set -l sync_rc $status + if test "$sync_rc" -ne 0 + echo "❗ 开启代理失败:当前 Fish 代理环境同步失败(返回码:$sync_rc)" >&2 + return "$sync_rc" + end +end + +function _clash_fish_run_off + _clash_fish_unset_shell_proxy + _clash_fish_set_persist_enabled false + _clashctl_real_off $argv +end + +function _clash_fish_auto_restore_proxy + _clash_fish_persist_enabled; or return 0 + _clash_fish_export_proxy; or return 0 +end + +function clashctl + set -l cmd + if set -q argv[1] + set cmd $argv[1] + end + + switch "$cmd" + case on + _clash_fish_run_on $argv[2..-1] + case off + _clash_fish_run_off $argv[2..-1] + case proxy + set -l subcmd + if set -q argv[2] + set subcmd $argv[2] + end + switch "$subcmd" + case on + _clash_fish_export_proxy + case off + _clash_fish_run_off $argv[3..-1] + case '*' + _clashctl_real $argv + end + case ui + _clashctl_real ui $argv[2..-1] + case status + _clashctl_real status $argv[2..-1] + case '*' + _clashctl_real $argv + end +end + +function clashon + clashctl on $argv +end + +function clashoff + clashctl off $argv +end + +function clashproxy + set -l subcmd show + if set -q argv[1] + set subcmd $argv[1] + end + + switch "$subcmd" + case on + clashctl proxy on + case off + clashctl off + case show status + clashctl proxy show + case groups + clashctl proxy groups + case current nodes select + clashctl proxy $argv + case '*' + echo "📜 用法:clashproxy [show|on|off|groups|current|nodes|select]" + echo "💡 主路径切节点请使用:clashselect 或 clashctl select" + return 2 + end +end + +function clashls + clashctl ls $argv +end + +function clashselect + clashctl select $argv +end + +function clashui + clashctl ui $argv +end + +function clashsecret + clashctl secret $argv +end + +function clashtun + clashctl tun $argv +end + +function clashrelay + clashctl relay $argv +end + +function clashupgrade + clashctl upgrade $argv +end + +function clashmixin + set -l subcmd + if set -q argv[1] + set subcmd $argv[1] + end + + switch "$subcmd" + case -e --edit + clashctl mixin edit + case -c --raw + clashctl mixin raw + case -r --runtime + clashctl mixin runtime + case '' + clashctl mixin + case '*' + clashctl mixin $argv + end +end + +_clash_fish_auto_restore_proxy diff --git a/scripts/core/alias.sh b/scripts/core/alias.sh index 247925e..d68d33d 100644 --- a/scripts/core/alias.sh +++ b/scripts/core/alias.sh @@ -90,7 +90,7 @@ _clash_alias_proxy_on_system() { } _clash_alias_proxy_off() { - _clashctl_real proxy off >/dev/null || true + _clash_alias_run_off "$@" } _clash_alias_proxy_show() { @@ -279,11 +279,7 @@ clashctl() { _clash_alias_proxy_show ;; off) - _clash_alias_unset_shell_proxy - _clash_alias_proxy_off - _clash_alias_print_sep - echo "🧹 系统代理已关闭" - ui_blank + _clash_alias_proxy_off "${@:3}" ;; *) _clashctl_real "$@" @@ -321,7 +317,7 @@ clashproxy() { clashctl proxy on ;; off) - clashctl proxy off + clashctl off ;; show|status) clashctl proxy show diff --git a/scripts/core/clashctl.sh b/scripts/core/clashctl.sh index da31090..6f82ee2 100644 --- a/scripts/core/clashctl.sh +++ b/scripts/core/clashctl.sh @@ -59,14 +59,14 @@ Usage: boot proxy on|off|status 📜 仅管理开机代理保持 upgrade 🚀 升级当前或指定内核 update 🔄 更新项目代码 - completion bash|zsh 💡 导出 Shell 补全脚本 + completion bash|zsh|fish 💡 导出 Shell 补全脚本 dev reset 🧪 恢复到安装前状态(保留项目目录和已下载文件) 🩺 Diagnose: doctor 🩺 诊断环境与运行状态 status 🔍️ 查看状态总览 log/logs 📜 查看日志 - completion 💡 导出 Bash / Zsh 补全脚本 + completion 💡 导出 Bash / Zsh / Fish 补全脚本 📌 Advanced Examples: clashctl sub list @@ -3026,7 +3026,7 @@ cmd_ui_help_summary() { printf ' %-18s %s\n' "clashctl sub" "🧩 订阅高级管理(启用 / 禁用 / 重命名 / 删除)" printf ' %-18s %s\n' "clashctl upgrade" "🚀 升级当前或指定内核" printf ' %-18s %s\n' "clashctl update" "🔄 更新项目代码" - printf ' %-18s %s\n' "clashctl completion" "💡 导出 Bash / Zsh 补全脚本" + printf ' %-18s %s\n' "clashctl completion" "💡 导出 Bash / Zsh / Fish 补全脚本" echo "📜 日志" printf ' %-18s %s\n' "clashctl doctor" "🩺 诊断面板" printf ' %-18s %s\n' "clashctl log/logs" "📜 查看日志" diff --git a/scripts/core/common.sh b/scripts/core/common.sh index fbc78c0..55be033 100644 --- a/scripts/core/common.sh +++ b/scripts/core/common.sh @@ -2401,6 +2401,10 @@ alias_source_file() { echo "$PROJECT_DIR/scripts/core/alias.sh" } +fish_alias_source_file() { + echo "$PROJECT_DIR/scripts/core/alias.fish" +} + completion_dir() { if [ "$INSTALL_SCOPE" = "system" ]; then echo "/etc/profile.d" @@ -2425,6 +2429,38 @@ zsh_completion_entry_file() { fi } +fish_completion_entry_file() { + if [ "$INSTALL_SCOPE" = "system" ]; then + echo "/etc/fish/completions/clash-for-linux.fish" + else + echo "$(completion_dir)/completion.fish" + fi +} + +fish_completion_link_file() { + if [ "$INSTALL_SCOPE" = "system" ]; then + echo "/etc/fish/completions/clash-for-linux.fish" + else + echo "$HOME/.config/fish/completions/clash-for-linux.fish" + fi +} + +fish_completion_link_files() { + local dir name + + if [ "$INSTALL_SCOPE" = "system" ]; then + dir="/etc/fish/completions" + else + dir="$HOME/.config/fish/completions" + fi + + for name in clashctl clashrelay clashmixin clashsecret clashupgrade clashtun; do + echo "$dir/$name.fish" + done + + echo "$(fish_completion_link_file)" +} + clashctl_entry_target() { echo "$(command_install_dir)/clashctl" } @@ -2529,17 +2565,22 @@ cleanup_legacy_shell_entries() { } install_shell_alias_entry() { - local profile_file alias_file shell_rc bash_completion_file zsh_completion_file + local profile_file fish_profile_file fish_conf_file alias_file fish_alias_file shell_rc bash_completion_file zsh_completion_file fish_completion_file cleanup_legacy_shell_entries profile_file="$(profile_entry_file)" + fish_profile_file="$(fish_profile_entry_file)" + fish_conf_file="$(fish_conf_entry_file)" alias_file="$(alias_source_file)" + fish_alias_file="$(fish_alias_source_file)" bash_completion_file="$(bash_completion_entry_file)" zsh_completion_file="$(zsh_completion_entry_file)" + fish_completion_file="$(fish_completion_entry_file)" mkdir -p "$(dirname "$profile_file")" [ -f "$alias_file" ] || die "未找到 alias 脚本:$alias_file" + [ -f "$fish_alias_file" ] || die "未找到 fish alias 脚本:$fish_alias_file" cat > "$profile_file" < "$fish_profile_file" < "$fish_conf_file" < "$bash_target" bash "$PROJECT_DIR/scripts/core/clashctl.sh" completion zsh > "$zsh_target" - - chmod +x "$bash_target" "$zsh_target" 2>/dev/null || true + bash "$PROJECT_DIR/scripts/core/clashctl.sh" completion fish > "$fish_target" + + chmod +x "$bash_target" "$zsh_target" "$fish_target" 2>/dev/null || true + + while IFS= read -r fish_link_target; do + [ -n "${fish_link_target:-}" ] || continue + [ "$fish_link_target" != "$fish_target" ] || continue + mkdir -p "$(dirname "$fish_link_target")" + cat > "$fish_link_target" </dev/null || true + rm -f "$fish_profile_file" "$fish_conf_file" 2>/dev/null || true if [ "$INSTALL_SCOPE" = "user" ]; then local shell_rc @@ -2609,7 +2697,12 @@ remove_shell_alias_entry() { } remove_clashctl_completion() { - rm -f "$(bash_completion_entry_file)" "$(zsh_completion_entry_file)" 2>/dev/null || true + rm -f "$(bash_completion_entry_file)" "$(zsh_completion_entry_file)" \ + "$(fish_completion_entry_file)" "$(fish_completion_link_file)" 2>/dev/null || true + while IFS= read -r fish_link_target; do + [ -n "${fish_link_target:-}" ] || continue + rm -f "$fish_link_target" 2>/dev/null || true + done < <(fish_completion_link_files) } install_has_subscription() { @@ -2655,6 +2748,22 @@ profile_entry_file() { fi } +fish_profile_entry_file() { + if [ "$INSTALL_SCOPE" = "system" ]; then + echo "/etc/fish/conf.d/clash-for-linux.fish" + else + echo "$HOME/.config/clash-for-linux/profile.fish" + fi +} + +fish_conf_entry_file() { + if [ "$INSTALL_SCOPE" = "system" ]; then + echo "/etc/fish/conf.d/clash-for-linux.fish" + else + echo "$HOME/.config/fish/conf.d/clash-for-linux.fish" + fi +} + install_rc_source_block() { local profile_file="$1" local source_target="$2" diff --git a/scripts/core/completion.sh b/scripts/core/completion.sh index 541f8be..3147675 100644 --- a/scripts/core/completion.sh +++ b/scripts/core/completion.sh @@ -294,7 +294,7 @@ _clash_for_linux_complete_completion() { COMPREPLY=() if [ "$rel_index" -eq 1 ]; then - _clash_for_linux_add_matches "$cur" bash zsh + _clash_for_linux_add_matches "$cur" bash zsh fish fi } @@ -416,6 +416,230 @@ EOF completion_emit_script_body } +completion_emit_fish_script() { + printf 'set -g _clash_for_linux_project_dir %q\n' "$PROJECT_DIR" + cat <<'EOF' +set -g _clash_for_linux_runtime_dir "$_clash_for_linux_project_dir/runtime" +set -g _clash_for_linux_subscription_file "$_clash_for_linux_runtime_dir/subscriptions.yaml" +set -g _clash_for_linux_mixin_file "$_clash_for_linux_runtime_dir/mixin.yaml" +set -g _clash_for_linux_local_subscription_dir "$_clash_for_linux_runtime_dir/subscriptions" +set -g _clash_for_linux_yq_bin "$_clash_for_linux_runtime_dir/bin/yq" + +# Hard constraints for completion: +# - local-only and offline +# - no controller or network access +# - low-latency best effort with immediate silent fallback +# - skip YAML-based dynamic completion when yq is missing or fails + +function _clash_for_linux_fish_matches + set -l cur "$argv[1]" + for candidate in $argv[2..-1] + test -n "$candidate"; or continue + if test -z "$cur"; or string match -q -- "$cur*" "$candidate" + echo "$candidate" + end + end +end + +function _clash_for_linux_fish_stream_matches + set -l cur "$argv[1]" + while read -l candidate + test -n "$candidate"; or continue + test "$candidate" != "null"; or continue + if test -z "$cur"; or string match -q -- "$cur*" "$candidate" + echo "$candidate" + end + end +end + +function _clash_for_linux_fish_subscription_matches + set -l cur "$argv[1]" + test -x "$_clash_for_linux_yq_bin"; or return 0 + test -s "$_clash_for_linux_subscription_file"; or return 0 + + "$_clash_for_linux_yq_bin" eval '.sources | keys | .[]' "$_clash_for_linux_subscription_file" 2>/dev/null \ + | _clash_for_linux_fish_stream_matches "$cur" +end + +function _clash_for_linux_fish_relay_matches + set -l cur "$argv[1]" + test -x "$_clash_for_linux_yq_bin"; or return 0 + test -s "$_clash_for_linux_mixin_file"; or return 0 + + "$_clash_for_linux_yq_bin" eval '(.append["proxy-groups"] // [])[] | select(.type == "relay") | .name' "$_clash_for_linux_mixin_file" 2>/dev/null \ + | _clash_for_linux_fish_stream_matches "$cur" +end + +function _clash_for_linux_fish_local_subscription_matches + set -l cur "$argv[1]" + test -d "$_clash_for_linux_local_subscription_dir"; or return 0 + + for path in "$_clash_for_linux_local_subscription_dir"/* + test -f "$path"; or continue + set -l candidate (basename "$path") + if test -z "$cur"; or string match -q -- "$cur*" "$candidate" + echo "$candidate" + end + end +end + +function _clash_for_linux_fish_top_level + set -l cur "$argv[1]" + _clash_for_linux_fish_matches "$cur" \ + add use ls health select on off status status-next \ + boot log logs doctor ui secret tun dev config lan mixin \ + relay profile sub proxy upgrade update completion help \ + -h --help +end + +function _clash_for_linux_fish_complete + set -l words (commandline -opc) + set -l cur (commandline -ct) + set -l root (basename "$words[1]" 2>/dev/null) + set -l canonical + set -l rel_index + set -l arg1 + + switch "$root" + case clashctl + if test (count $words) -le 1 + _clash_for_linux_fish_top_level "$cur" + return 0 + end + set canonical "$words[2]" + set rel_index (math (count $words) - 1) + set arg1 "$words[3]" + case clashrelay + set canonical relay + set rel_index (count $words) + set arg1 "$words[2]" + case clashmixin + set canonical mixin + set rel_index (count $words) + set arg1 "$words[2]" + case clashsecret + set canonical secret + set rel_index (count $words) + set arg1 "$words[2]" + case clashupgrade + set canonical upgrade + set rel_index (count $words) + set arg1 "$words[2]" + case clashtun + set canonical tun + set rel_index (count $words) + set arg1 "$words[2]" + case '*' + return 0 + end + + switch "$canonical" + case add + if test "$rel_index" -eq 1 + _clash_for_linux_fish_matches "$cur" local + else if test "$arg1" = local + _clash_for_linux_fish_local_subscription_matches "$cur" + end + case use + _clash_for_linux_fish_matches "$cur" --recommend -r --verbose -v + if not string match -q -- '-*' "$cur" + _clash_for_linux_fish_subscription_matches "$cur" + end + case health + _clash_for_linux_fish_matches "$cur" --verbose -v + if not string match -q -- '-*' "$cur" + _clash_for_linux_fish_subscription_matches "$cur" + end + case status + _clash_for_linux_fish_matches "$cur" --verbose -v + case boot + if test "$rel_index" -eq 1 + _clash_for_linux_fish_matches "$cur" on off status runtime proxy help -h --help + else if contains -- "$arg1" runtime proxy; and test "$rel_index" -eq 2 + _clash_for_linux_fish_matches "$cur" on off status + end + case config + if test "$rel_index" -eq 1 + _clash_for_linux_fish_matches "$cur" show explain regen kernel + else if test "$arg1" = kernel; and test "$rel_index" -eq 2 + _clash_for_linux_fish_matches "$cur" mihomo clash + end + case lan + if test "$rel_index" -eq 1 + _clash_for_linux_fish_matches "$cur" on off status enable disable help -h --help + end + case mixin + if test "$rel_index" -eq 1 + _clash_for_linux_fish_matches "$cur" edit raw runtime help -e -c -r --edit --raw --runtime -h --help + end + case relay + if test "$rel_index" -eq 1 + _clash_for_linux_fish_matches "$cur" add list ls remove rm delete help -h --help + else + switch "$arg1" + case remove rm delete + if test "$rel_index" -eq 2 + _clash_for_linux_fish_relay_matches "$cur" + end + case add + _clash_for_linux_fish_matches "$cur" --domain --match + end + end + case sub + if test "$rel_index" -eq 1 + _clash_for_linux_fish_matches "$cur" list use set enable disable rename remove rm del health help -h --help + else + switch "$arg1" + case use enable disable remove rm del rename + if test "$rel_index" -eq 2 + _clash_for_linux_fish_subscription_matches "$cur" + end + case health + _clash_for_linux_fish_matches "$cur" --verbose -v + if test "$rel_index" -eq 2; and not string match -q -- '-*' "$cur" + _clash_for_linux_fish_subscription_matches "$cur" + end + end + end + case tun + if test "$rel_index" -eq 1 + _clash_for_linux_fish_matches "$cur" status on off doctor + end + case upgrade + _clash_for_linux_fish_matches "$cur" mihomo clash -v --verbose + case update + _clash_for_linux_fish_matches "$cur" --force --regenerate + case dev + if test "$rel_index" -eq 1 + _clash_for_linux_fish_matches "$cur" reset + end + case completion + if test "$rel_index" -eq 1 + _clash_for_linux_fish_matches "$cur" bash zsh fish + end + case help + if test "$rel_index" -eq 1 + _clash_for_linux_fish_matches "$cur" advanced + end + end +end + +complete -c clashctl -e +complete -c clashrelay -e +complete -c clashmixin -e +complete -c clashsecret -e +complete -c clashupgrade -e +complete -c clashtun -e + +complete -c clashctl -f -a '(_clash_for_linux_fish_complete)' +complete -c clashrelay -f -a '(_clash_for_linux_fish_complete)' +complete -c clashmixin -f -a '(_clash_for_linux_fish_complete)' +complete -c clashsecret -f -a '(_clash_for_linux_fish_complete)' +complete -c clashupgrade -f -a '(_clash_for_linux_fish_complete)' +complete -c clashtun -f -a '(_clash_for_linux_fish_complete)' +EOF +} + cmd_completion() { case "${1:-}" in bash) @@ -424,8 +648,11 @@ cmd_completion() { zsh) completion_emit_zsh_script ;; + fish) + completion_emit_fish_script + ;; *) - die_usage "completion 参数不合法" "clashctl completion bash|zsh" + die_usage "completion 参数不合法" "clashctl completion bash|zsh|fish" ;; esac }