How to get a list of "opened" and "visible" modules at a specific point using Merlin?

I am using merlin-lib to provide coding assistant in a Web REPL. I think it would be very helpful for the users to know what modules are open and what modules are available at a point of the program.

IMO this will greatly increase the productivity for exploratory programming for people occasionally using the system (and I often find myself hoping such feature exists).

Being able to perform accurate (theoretically complete I guess?) context aware completion, Merlin must already have this information available internally as far as my guesswork goes. But looking at the query protocol, it’s not clear how these information could be extracted.

(* excerpt of merlin-lib/query_protoco/query_protocol.ml of my local
   opam switch, which is having merlin-lib.4.7-414 installed *)
type _ t =
  | Type_expr(* *)
    :  string * Msource.position
    -> string t
  | Type_enclosing(* *)
    :  (string * int) option * Msource.position * int option
    -> (Location.t * [`String of string | `Index of int] * is_tail_position) list t
  | Enclosing(* *)
    :  Msource.position
    -> Location.t list t
  | Complete_prefix(* *)
    :  string * Msource.position * Compl.kind list *
       [`with_documentation] _bool * [`with_types] _bool
    -> completions t
  | Expand_prefix(* *)
    :  string * Msource.position * Compl.kind list * [`with_types] _bool
    -> completions t
  | Polarity_search
    :  string * Msource.position
    -> completions t
  | Refactor_open
    :  [`Qualify | `Unqualify] * Msource.position
    -> (string * Location.t) list t
  | Document(* *)
    : string option * Msource.position
    -> [ `Found of string
       | `Invalid_context
       | `Builtin of string
       | `Not_in_env of string
       | `File_not_found of string
       | `Not_found of string * string option
       | `No_documentation
       ] t
  | Locate_type
    : Msource.position
      -> [ `Found of string option * Lexing.position
         | `Invalid_context
         | `Builtin of string
         | `Not_in_env of string
         | `File_not_found of string
         | `Not_found of string * string option
         | `At_origin
         ] t
  | Locate(* *)
    : string option * [ `ML | `MLI ] * Msource.position
    -> [ `Found of string option * Lexing.position
       | `Invalid_context
       | `Builtin of string
       | `Not_in_env of string
       | `File_not_found of string
       | `Not_found of string * string option
       | `At_origin
       ] t
  | Jump(* *)
    : string * Msource.position
    -> [ `Found of Lexing.position
       | `Error of string
       ] t
  | Phrase(* *)
    : [`Next | `Prev] * Msource.position
    -> Lexing.position t
  | Case_analysis(* *)
    : Msource.position * Msource.position -> (Location.t * string) t
  | Holes(* *)
    : (Location.t * string) list t
  | Construct
    : Msource.position * [`None | `Local] option * int option
    -> (Location.t * string list) t
  | Outline(* *)
    :  outline t
  | Shape(* *)
    :  Msource.position
    -> shape list t
  | Errors(* *)
    :  error_filter
    -> Location.error list t
  | Dump
    :  Std.json list
    -> Std.json t
  | Path_of_source(* *)
    :  string list
    -> string t
  | List_modules(* *)
    :  string list
    -> string list t
  | Findlib_list
    :  string list t
  | Extension_list
    :  [`All|`Enabled|`Disabled]
    -> string list t
  | Path_list
    :  [`Build|`Source]
    -> string list t
  | Occurrences(* *)
    : [`Ident_at of Msource.position]
    -> Location.t list t
  | Version
    : string t

As extras, in the case of opened modules, it would be especially helpful to know how a certain module is opened in addition. E.g. by a standalone open statement? by a local open? by an open flag passed to ocamlc? etc.

For available modules, it would be helpful to know how they became available.

These extra information would allow a better heuristic in deciding the order of presentation to the user (e.g. contents of modules being recent locally opened is likely to be very helpful for the user and we can prioritize displaying of assisting information for them).

2 Likes

Just as a side remark for the sake of code readability, globally opening any module is a very bad idea.
Just use local opens or the ModuleName.(…) construction.
Then the programmer does not need to keep track in his head which modules are opened (because there are none).

I don’t believe @haochenx is advocating “globally opening modules”; I believe the problem is: Trying to build a WebIDE for OCaml; working on something related to auto completion in CodeMirror, and then needing to query merlin-js for this info.

1 Like