This commit is contained in:
atagen 2026-03-23 16:24:00 +11:00
parent 4e41fcda6d
commit 144d72b223
5 changed files with 188 additions and 258 deletions

View file

@ -233,7 +233,11 @@ let num_cores () =
with _ -> 4
let try_native_completion bin_path =
let patterns = [
List.find_map (fun args ->
match run_cmd args 500 with
| Some text when is_nushell_source text -> Some text
| _ -> None
) [
[bin_path; "completions"; "nushell"];
[bin_path; "completion"; "nushell"];
[bin_path; "--completions"; "nushell"];
@ -241,30 +245,35 @@ let try_native_completion bin_path =
[bin_path; "generate-completion"; "nushell"];
[bin_path; "--generate-completion"; "nushell"];
[bin_path; "shell-completions"; "nushell"];
] in
let rec go = function
| [] -> None
| args :: rest ->
match run_cmd args 500 with
| Some text when is_nushell_source text -> Some text
| _ -> go rest
in
go patterns
]
let cmd_manpage file =
let parse_manpage_for_command file =
let contents = read_manpage_file file in
let fallback = cmd_name_of_manpage file in
let cmd = match extract_synopsis_command contents with
| Some name -> name | None -> fallback in
if not (is_nushell_builtin cmd) then
if is_nushell_builtin cmd then None
else
let result = parse_manpage_string contents in
if result.entries <> [] then
print_string (generate_extern cmd result)
let sub_sections = extract_subcommand_sections contents in
let result = if sub_sections <> [] then
{ result with subcommands = List.map (fun (name, desc, _) ->
{ name; desc }) sub_sections }
else result in
let subs = List.map (fun (name, _desc, r) ->
(cmd ^ " " ^ name, r)) sub_sections in
Some (cmd, result, subs)
let cmd_manpage file =
match parse_manpage_for_command file with
| Some (cmd, result, _) when result.entries <> [] ->
print_string (generate_extern cmd result)
| _ -> ()
let cmd_manpage_dir dir =
List.iter (fun section ->
let subdir = Filename.concat dir (Printf.sprintf "man%d" section) in
if Sys.file_exists subdir && Sys.is_directory subdir then
if is_dir subdir then
Array.iter (fun file ->
(try cmd_manpage (Filename.concat subdir file) with _ -> ())
) (Sys.readdir subdir)
@ -274,28 +283,16 @@ let max_resolve_results = 500
let process_manpage file =
try
let contents = read_manpage_file file in
let fallback = cmd_name_of_manpage file in
let cmd = match extract_synopsis_command contents with
| Some name -> name | None -> fallback in
if is_nushell_builtin cmd then None
else
let result = parse_manpage_string contents in
let sub_sections = extract_subcommand_sections contents in
let result = if sub_sections <> [] then
{ result with subcommands = List.map (fun (name, desc, _) ->
{ name; desc }) sub_sections }
else result in
let subs = List.map (fun (name, _desc, r) ->
(cmd ^ " " ^ name, r)) sub_sections in
if result.entries <> [] || subs <> [] then Some (cmd, result, subs)
else None
match parse_manpage_for_command file with
| Some (cmd, result, subs) when result.entries <> [] || subs <> [] ->
Some (cmd, result, subs)
| _ -> None
with _ -> None
let manpaged_commands mandir =
List.fold_left (fun acc section ->
let subdir = Filename.concat mandir (Printf.sprintf "man%d" section) in
if Sys.file_exists subdir && Sys.is_directory subdir then
if is_dir subdir then
Array.fold_left (fun acc f -> SSet.add (cmd_name_of_manpage f) acc)
acc (Sys.readdir subdir)
else acc
@ -403,12 +400,12 @@ let cmd_index bindirs mandirs ignorelist help_only dir =
let done_cmds = ref SSet.empty in
let n_results = ref 0 in
let index_bindir bindir mandir =
if not (Sys.file_exists bindir && Sys.is_directory bindir) then
if not (is_dir bindir) then
Printf.eprintf "skipping %s (not found)\n" bindir
else begin
let bins = Sys.readdir bindir in
Array.sort String.compare bins;
let manpaged = if Sys.file_exists mandir && Sys.is_directory mandir
let manpaged = if is_dir mandir
then manpaged_commands mandir else SSet.empty in
let max_jobs = num_cores () in
let classified = Array.map (fun name ->
@ -502,10 +499,10 @@ let cmd_index bindirs mandirs ignorelist help_only dir =
end
done;
(* Phase 2: manpages *)
if Sys.file_exists mandir && Sys.is_directory mandir then
if is_dir mandir then
List.iter (fun section ->
let subdir = Filename.concat mandir (Printf.sprintf "man%d" section) in
if Sys.file_exists subdir && Sys.is_directory subdir then begin
if is_dir subdir then begin
let files = Sys.readdir subdir in
Array.sort String.compare files;
Array.iter (fun file ->
@ -544,14 +541,11 @@ let cmd_dump dirs =
let find_in_path name =
try
let path_var = Sys.getenv "PATH" in
let dirs = String.split_on_char ':' path_var in
let rec go = function
| [] -> None
| dir :: rest ->
let p = Filename.concat dir name in
if is_executable p then Some p else go rest in
go dirs
Sys.getenv "PATH"
|> String.split_on_char ':'
|> List.find_map (fun dir ->
let p = Filename.concat dir name in
if is_executable p then Some p else None)
with Not_found -> None
let resolve_and_cache ~dir name path =
@ -567,50 +561,47 @@ let completion_json value desc =
(escape_json value) (escape_json desc)
let flag_completions prefix entries =
let candidates = ref [] in
List.iter (fun (e : entry) ->
List.filter_map (fun (e : entry) ->
let desc = match e.param with
| Some (Mandatory p) -> if e.desc <> "" then e.desc ^ " <" ^ p ^ ">" else "<" ^ p ^ ">"
| Some (Optional p) -> if e.desc <> "" then e.desc ^ " [" ^ p ^ "]" else "[" ^ p ^ "]"
| None -> e.desc in
(match e.switch with
| Long l ->
let flag = "--" ^ l in
if String.starts_with ~prefix flag then
candidates := completion_json flag desc :: !candidates
| Short c ->
let flag = Printf.sprintf "-%c" c in
if String.starts_with ~prefix flag then
candidates := completion_json flag desc :: !candidates
| Both (c, l) ->
let long = "--" ^ l in
let short = Printf.sprintf "-%c" c in
if String.starts_with ~prefix long then
candidates := completion_json long desc :: !candidates
else if String.starts_with ~prefix short then
candidates := completion_json short desc :: !candidates)
) entries;
List.rev !candidates
match e.switch with
| Long l ->
let flag = "--" ^ l in
if String.starts_with ~prefix flag then Some (completion_json flag desc) else None
| Short c ->
let flag = Printf.sprintf "-%c" c in
if String.starts_with ~prefix flag then Some (completion_json flag desc) else None
| Both (c, l) ->
let long = "--" ^ l in
let short = Printf.sprintf "-%c" c in
if String.starts_with ~prefix long then Some (completion_json long desc)
else if String.starts_with ~prefix short then Some (completion_json short desc)
else None
) entries
let cmd_complete spans user_dir system_dirs =
match spans with
| [] -> print_string "[]\n"
| cmd_name :: rest ->
let dirs = user_dir :: system_dirs in
(* Try longest subcommand match first: "git add" before "git" *)
let rec find_result tokens =
match tokens with
| [] -> None
| _ ->
let try_name = String.concat " " tokens in
match lookup dirs try_name with
| Some r -> Some (try_name, r, List.length tokens)
| None ->
find_result (List.rev (List.tl (List.rev tokens))) in
(* Try longest prefix match: "git add" before "git" *)
let find_result tokens =
let n = List.length tokens in
List.init n Fun.id |> List.find_map (fun drop ->
let prefix = List.filteri (fun i _ -> i < n - drop) tokens in
match prefix with
| [] -> None
| _ ->
let try_name = String.concat " " prefix in
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
let partial_tokens = cmd_name :: (match rest with
| _ :: _ -> List.rev (List.tl (List.rev rest))
| _ -> []) in
let partial_tokens = match rest with
| _ :: _ -> cmd_name :: List.rev (List.tl (List.rev rest))
| _ -> [cmd_name] in
let last_token = match rest with
| [] -> "" | _ -> List.nth rest (List.length rest - 1) in
(* Try full token list first (last token is a complete subcommand),
@ -630,25 +621,24 @@ let cmd_complete spans user_dir system_dirs =
| Some _pairs -> try_both ()
| None -> (None, partial))
| None -> (None, partial)) in
(match result with
| None -> print_string "[]\n"
| Some (_matched_name, r, _depth) ->
let candidates = ref [] in
if String.starts_with ~prefix:"-" partial then
candidates := flag_completions partial r.entries
else begin
let subs = match r.subcommands with
| _ :: _ -> r.subcommands
| [] -> subcommands_of dirs _matched_name in
List.iter (fun (sc : subcommand) ->
if partial = "" || String.starts_with ~prefix:partial sc.name then
candidates := completion_json sc.name sc.desc :: !candidates
) subs;
candidates := List.rev !candidates;
if partial = "" || !candidates = [] then
candidates := !candidates @ flag_completions partial r.entries
end;
Printf.printf "[%s]\n" (String.concat "," !candidates))
let candidates = match result with
| None -> []
| Some (_matched_name, r, _depth) ->
if String.starts_with ~prefix:"-" partial then
flag_completions partial r.entries
else
let subs = match r.subcommands with
| _ :: _ -> r.subcommands
| [] -> subcommands_of dirs _matched_name in
let sub_candidates = List.filter_map (fun (sc : subcommand) ->
if partial = "" || String.starts_with ~prefix:partial sc.name then
Some (completion_json sc.name sc.desc)
else None
) subs in
if partial = "" || sub_candidates = [] then
sub_candidates @ flag_completions partial r.entries
else sub_candidates in
Printf.printf "[%s]\n" (String.concat "," candidates)
let cmd_query cmd dirs =
match lookup_raw dirs cmd with
@ -659,15 +649,12 @@ let cmd_query cmd dirs =
let load_ignorelist path =
try
let ic = open_in path in
let lines = ref [] in
(try while true do
let line = String.trim (input_line ic) in
if String.length line > 0 && line.[0] <> '#' then
lines := line :: !lines
done with End_of_file -> ());
close_in ic;
SSet.of_list !lines
In_channel.with_open_text path In_channel.input_all
|> String.split_on_char '\n'
|> List.filter_map (fun line ->
let line = String.trim line in
if String.length line > 0 && line.[0] <> '#' then Some line else None)
|> SSet.of_list
with _ -> SSet.empty
let parse_index_args args =