use relative /share path to look for manuals when resolving on-the-fly

This commit is contained in:
atagen 2026-03-29 19:48:57 +11:00
parent cadc5f98d4
commit 9dd1261a46

View file

@ -1087,15 +1087,27 @@ let cmd_complete spans user_dir system_dirs mandirs =
match lookup dirs try_name with
| Some r -> Some (try_name, r, List.length prefix)
| None -> None) in
let all_tokens = cmd_name :: rest in
(* strip flag tokens (--user, -a, etc.) from intermediate positions.
* flags are not part of the subcommand path and should not affect
* lookup. e.g. "systemctl --user start" should look up "systemctl start".
* the last token (partial) is NOT stripped it may be a flag the
* user is typing (e.g. "--u") which needs fuzzy matching. *)
let strip_intermediate_flags tokens =
match List.rev tokens with
| last :: rev_rest ->
List.filter (fun t ->
String.length t = 0 || t.[0] <> '-') (List.rev rev_rest)
@ [last]
| [] -> [] in
let all_tokens = strip_intermediate_flags (cmd_name :: rest) in
let last_token = match rest with
| [] -> "" | _ -> List.nth rest (List.length rest - 1) in
(* only treat the last token as a completed subcommand when nushell
* sends a trailing empty token (cursor is after a space).
* otherwise the user is still typing and we treat it as partial. *)
let lookup_tokens = if last_token = "" then all_tokens
else match rest with
| _ :: _ -> cmd_name :: List.rev (List.tl (List.rev rest))
else match all_tokens with
| _ :: _ -> List.rev (List.tl (List.rev all_tokens))
| _ -> [cmd_name] in
let resolve tokens partial =
match find_result tokens with
@ -1112,7 +1124,34 @@ let cmd_complete spans user_dir system_dirs mandirs =
(* no match, or only a parent matched — try on-the-fly resolution *)
(match find_in_path cmd_name with
| Some path ->
(match resolve_and_cache ~dir:user_dir ~mandirs cmd_name path with
(* derive sibling share/man from the binary's location.
* e.g. /nix/store/.../bin/foo /nix/store/.../share/man
* this lets on-the-fly resolution find manpages for commands
* not in the indexed prefixes. also resolves through nix
* wrappers to find the real binary's manpage location. *)
let mandir_of_bin p =
let bindir = Filename.dirname p in
let prefix = Filename.dirname bindir in
Filename.concat (Filename.concat prefix "share") "man" in
let bin_mandirs =
let direct = mandir_of_bin path in
(* also check the canonical path after resolving symlinks.
* e.g. /run/current-system/sw/bin/foo is a symlink to
* /nix/store/xxx/bin/foo check /nix/store/xxx/share/man *)
let via_realpath =
try let real = Unix.realpath path in
if real <> path then [mandir_of_bin real] else []
with Unix.Unix_error _ -> [] in
let via_wrapper =
match nix_script_wrapper_target path with
| Some target -> [mandir_of_bin target]
| None ->
match nix_wrapper_target path with
| Some target -> [mandir_of_bin target]
| None -> [] in
List.filter is_dir (direct :: via_realpath @ via_wrapper) in
let all_mandirs = bin_mandirs @ mandirs in
(match resolve_and_cache ~dir:user_dir ~mandirs:all_mandirs cmd_name path with
| Some _pairs -> resolve lookup_tokens last_token
| None -> (found, partial))
| None -> (found, partial)) in