diff --git a/DESIGN_COMPLETE.md b/DESIGN_COMPLETE.md index 4796b77..36bd848 100644 --- a/DESIGN_COMPLETE.md +++ b/DESIGN_COMPLETE.md @@ -108,9 +108,14 @@ Final separator is `/` and the final component has no `.`. We complete a path - `/` → **root names**: the page-root (package) and lib-root (library) names from `Show.scan` (`/odoc`, `/odoc.model`, `/ocaml-compiler`, …), filtered by `frag`. -- `//` → **units/pages in that root**: directory entries of the - root's dir (from `scan`'s `page_roots`/`lib_roots`), i.e. the `.odoc` files' - names (capitalised; `page-`/`impl-` handled) and sub-directories. +- `///…/` → **entries of a directory under the root**: the + first segment resolves to the root's dir (from `scan`'s + `page_roots`/`lib_roots`); any further segments are literal sub-directories + beneath it (`/odoc/deprecated/` → `odoc/odoc/deprecated`). The directory's + entries are offered: `.odoc` files' names (capitalised units; `page-` → + page name; `impl-`/`asset-` skipped) and sub-directories, hidden (`__`) names + filtered. So pages nested under a package (`/odoc/deprecated/index`) complete + at any depth. When the final component *does* contain a `.` (`/odoc.model/Foo.ba`), the last separator is that `.`, so it falls through to case 1 with a path-qualified @@ -192,7 +197,7 @@ needed for v1. |---|---|---| | `X.Y.frag`, `kind-X.kind-frag` | yes | the core case; reuses `show` | | `/frag` (root names) | yes | from `scan` roots | -| `/root/frag` (units in root) | yes | directory listing | +| `/root/…/frag` (entries under a root, any depth) | yes | directory listing | | `/root/Unit.frag` (path + dotted) | yes | path-qualified resolve + children | | bare `frag` (top-level) | last | units + `Stdlib` members; broad | | `./…`, `//…` (relative paths) | no | no "current unit" in `complete` | diff --git a/lib/complete.ml b/lib/complete.ml index 101639e..158a21a 100644 --- a/lib/complete.ml +++ b/lib/complete.ml @@ -201,30 +201,42 @@ let complete_root_names scan ~stem ~comp = |> List.sort_uniq compare |> List.map (fun n -> stem ^ n) -let complete_in_root scan ~stem ~comp ~root = - match List.assoc_opt root (scan.Refs.page_roots @ scan.Refs.lib_roots) with - | None -> [] - | Some dir -> ( - match Bos.OS.Dir.contents dir with - | Error _ -> [] - | Ok entries -> - List.filter_map - (fun p -> - let is_dir = - match Bos.OS.Dir.exists p with Ok b -> b | _ -> false - in - entry_name p ~is_dir) - entries - |> List.filter (fun n -> (not (hidden n)) && starts_with ~prefix:comp n) - |> List.sort_uniq compare - |> List.map (fun n -> stem ^ n)) +(* List a directory's entries (units, pages, sub-dirs) as path segments. *) +let complete_dir dir ~stem ~comp = + match Bos.OS.Dir.contents dir with + | Error _ -> [] + | Ok entries -> + List.filter_map + (fun p -> + let is_dir = + match Bos.OS.Dir.exists p with Ok b -> b | _ -> false + in + entry_name p ~is_dir) + entries + |> List.filter (fun n -> (not (hidden n)) && starts_with ~prefix:comp n) + |> List.sort_uniq compare + |> List.map (fun n -> stem ^ n) let complete_path scan ~stem ~comp = + (* [stem] is the path up to and including the last [/]. The first segment is a + root name ([odoc], [odoc.model], …); any further segments are literal + sub-directories beneath it ([/odoc/deprecated/] -> odoc/odoc/deprecated). *) match String.split_on_char '/' stem with - | [ ""; "" ] -> complete_root_names scan ~stem ~comp (* "/" *) - | [ ""; root; "" ] when root <> "" -> - complete_in_root scan ~stem ~comp ~root (* "/root/" *) - | _ -> [] (* "//", "./", deeper: out of scope *) + | "" :: "" :: _ -> [] (* "//…" current package: out of scope *) + | "" :: rest -> ( + match List.filter (( <> ) "") rest with + | [] -> complete_root_names scan ~stem ~comp (* "/" *) + | root :: subs -> ( + match + List.assoc_opt root (scan.Refs.page_roots @ scan.Refs.lib_roots) + with + | None -> [] + | Some base -> + if List.exists (fun s -> s = "." || s = "..") subs then [] + else + let dir = List.fold_left (fun d s -> Fpath.(d / s)) base subs in + complete_dir dir ~stem ~comp)) + | _ -> [] (* "./…" relative, or no leading slash: out of scope *) (* {1 Splitting and dispatch} *)