Blame browser/viewer.ml

Packit bd2e5d
(*************************************************************************)
Packit bd2e5d
(*                                                                       *)
Packit bd2e5d
(*                         OCaml LablTk library                          *)
Packit bd2e5d
(*                                                                       *)
Packit bd2e5d
(*            Jacques Garrigue, Kyoto University RIMS                    *)
Packit bd2e5d
(*                                                                       *)
Packit bd2e5d
(*   Copyright 1999 Institut National de Recherche en Informatique et    *)
Packit bd2e5d
(*   en Automatique and Kyoto University.  All rights reserved.          *)
Packit bd2e5d
(*   This file is distributed under the terms of the GNU Library         *)
Packit bd2e5d
(*   General Public License, with the special exception on linking       *)
Packit bd2e5d
(*   described in file ../../../LICENSE.                                 *)
Packit bd2e5d
(*                                                                       *)
Packit bd2e5d
(*************************************************************************)
Packit bd2e5d
Packit bd2e5d
(* $Id$ *)
Packit bd2e5d
Packit bd2e5d
open StdLabels
Packit bd2e5d
open Tk
Packit bd2e5d
open Jg_tk
Packit bd2e5d
open Mytypes
Packit bd2e5d
open Longident
Packit bd2e5d
open Types
Packit bd2e5d
open Typedtree
Packit bd2e5d
open Env
Packit bd2e5d
open Searchpos
Packit bd2e5d
open Searchid
Packit bd2e5d
Packit bd2e5d
(* Managing the module list *)
Packit bd2e5d
Packit bd2e5d
let list_modules ~path =
Packit bd2e5d
  List.fold_left path ~init:[] ~f:
Packit bd2e5d
  begin fun modules dir ->
Packit bd2e5d
    let l =
Packit bd2e5d
      List.filter (Useunix.get_files_in_directory dir)
Packit bd2e5d
        ~f:(fun x -> Filename.check_suffix x ".cmi") in
Packit bd2e5d
    let l = List.map l ~f:
Packit bd2e5d
    begin fun x ->
Packit bd2e5d
      String.capitalize_ascii (Filename.chop_suffix x ".cmi")
Packit bd2e5d
    end in
Packit bd2e5d
    List.fold_left l ~init:modules
Packit bd2e5d
     ~f:(fun modules item ->
Packit bd2e5d
          if List.mem item modules then modules else item :: modules)
Packit bd2e5d
  end
Packit bd2e5d
Packit bd2e5d
let reset_modules box =
Packit bd2e5d
  Listbox.delete box ~first:(`Num 0) ~last:`End;
Packit bd2e5d
  module_list := List.sort (Jg_completion.compare_string ~nocase:true)
Packit bd2e5d
      (list_modules ~path:!Config.load_path);
Packit bd2e5d
  Listbox.insert box ~index:`End ~texts:!module_list;
Packit bd2e5d
  Jg_box.recenter box ~index:(`Num 0)
Packit bd2e5d
Packit bd2e5d
Packit bd2e5d
(* How to display a symbol *)
Packit bd2e5d
Packit bd2e5d
let view_symbol ~kind ~env ?path id =
Packit bd2e5d
  let name = match id with
Packit bd2e5d
      Lident x -> x
Packit bd2e5d
    | Ldot (_, x) -> x
Packit bd2e5d
    | _ -> match kind with Pvalue | Ptype | Plabel -> "z" | _ -> "Z"
Packit bd2e5d
  in
Packit bd2e5d
  match kind with
Packit bd2e5d
    Pvalue ->
Packit bd2e5d
      let path, vd = lookup_value id env in
Packit bd2e5d
      view_signature_item ~path ~env [Sig_value (Ident.create name, vd)]
Packit bd2e5d
  | Ptype -> view_type_id id ~env
Packit bd2e5d
  | Plabel -> let ld = lookup_label id env in
Packit bd2e5d
      begin match ld.lbl_res.desc with
Packit bd2e5d
        Tconstr (path, _, _) -> view_type_decl path ~env
Packit bd2e5d
      | _ -> ()
Packit bd2e5d
      end
Packit bd2e5d
  | Pconstructor ->
Packit bd2e5d
      let cd = lookup_constructor id env in
Packit bd2e5d
      begin match cd.cstr_tag, cd.cstr_res.desc with
Packit bd2e5d
	Cstr_extension _, Tconstr (cpath, args, _) ->
Packit bd2e5d
          view_signature ~title:(string_of_longident id) ~env ?path
Packit bd2e5d
            [Sig_typext (Ident.create name,
Packit bd2e5d
			 {Types.ext_type_path = cpath;
Packit bd2e5d
			  ext_type_params = args;
Packit bd2e5d
			  ext_args = Cstr_tuple cd.cstr_args;
Packit bd2e5d
			  ext_ret_type = (if cd.cstr_generalized
Packit bd2e5d
			                  then Some cd.cstr_res else None);
Packit bd2e5d
			  ext_private = cd.cstr_private;
Packit bd2e5d
			  ext_loc = cd.cstr_loc;
Packit bd2e5d
			  ext_attributes = cd.cstr_attributes},
Packit bd2e5d
			 if Path.same cpath Predef.path_exn then Text_exception
Packit bd2e5d
			 else Text_first)]
Packit bd2e5d
      | _, Tconstr (cpath, _, _) ->
Packit bd2e5d
          view_type_decl cpath ~env
Packit bd2e5d
      | _ -> ()
Packit bd2e5d
      end
Packit bd2e5d
  | Pmodule -> view_module_id id ~env
Packit bd2e5d
  | Pmodtype -> view_modtype_id id ~env
Packit bd2e5d
  | Pclass -> view_class_id id ~env
Packit bd2e5d
  | Pcltype -> view_cltype_id id ~env
Packit bd2e5d
Packit bd2e5d
Packit bd2e5d
(* Create a list of symbols you can choose from *)
Packit bd2e5d
Packit bd2e5d
let choose_symbol ~title ~env ?signature ?path l =
Packit bd2e5d
  if match path with
Packit bd2e5d
    None -> false
Packit bd2e5d
  | Some path -> is_shown_module path
Packit bd2e5d
  then () else
Packit bd2e5d
  let tl = Jg_toplevel.titled title in
Packit bd2e5d
  Jg_bind.escape_destroy tl;
Packit bd2e5d
  top_widgets := coe tl :: !top_widgets;
Packit bd2e5d
  let buttons = Frame.create tl in
Packit bd2e5d
  let all = Button.create buttons ~text:"Show all" ~padx:20
Packit bd2e5d
  and ok = Jg_button.create_destroyer tl ~parent:buttons
Packit bd2e5d
  and detach = Button.create buttons ~text:"Detach"
Packit bd2e5d
  and edit = Button.create buttons ~text:"Impl"
Packit bd2e5d
  and intf = Button.create buttons ~text:"Intf" in
Packit bd2e5d
  let l = List.sort l ~cmp:(fun (li1, _) (li2,_) -> compare li1 li2) in
Packit bd2e5d
  let nl = List.map l ~f:
Packit bd2e5d
    begin fun (li, k) ->
Packit bd2e5d
      string_of_longident li ^ " (" ^ string_of_kind k ^ ")"
Packit bd2e5d
    end in
Packit bd2e5d
  let fb = Frame.create tl in
Packit bd2e5d
  let box =
Packit bd2e5d
    new Jg_multibox.c fb ~cols:3 ~texts:nl ~maxheight:3 ~width:21 in
Packit bd2e5d
  box#init;
Packit bd2e5d
  box#bind_kbd ~events:[`KeyPressDetail"Escape"]
Packit bd2e5d
    ~action:(fun _ ~index -> destroy tl; break ());
Packit bd2e5d
  if List.length nl > 9 then ignore (Jg_multibox.add_scrollbar box);
Packit bd2e5d
  Jg_multibox.add_completion box ~action:
Packit bd2e5d
    begin fun pos ->
Packit bd2e5d
      let li, k = List.nth l pos in
Packit bd2e5d
      let path =
Packit bd2e5d
        match path, li with
Packit bd2e5d
          None, Ldot (lip, _) ->
Packit bd2e5d
            begin try
Packit bd2e5d
              Some (lookup_module ~load:true lip env)
Packit bd2e5d
            with Not_found -> None
Packit bd2e5d
            end
Packit bd2e5d
        | _ -> path
Packit bd2e5d
      in view_symbol li ~kind:k ~env ?path
Packit bd2e5d
    end;
Packit bd2e5d
  pack [buttons] ~side:`Bottom ~fill:`X;
Packit bd2e5d
  pack [fb] ~side:`Top ~fill:`Both ~expand:true;
Packit bd2e5d
  begin match signature with
Packit bd2e5d
    None -> pack [ok] ~fill:`X ~expand:true
Packit bd2e5d
  | Some signature ->
Packit bd2e5d
      Button.configure all ~command:
Packit bd2e5d
        begin fun () ->
Packit bd2e5d
          view_signature signature ~title ~env ?path
Packit bd2e5d
        end;
Packit bd2e5d
      pack [ok; all] ~side:`Right ~fill:`X ~expand:true
Packit bd2e5d
  end;
Packit bd2e5d
  begin match path with None -> ()
Packit bd2e5d
  | Some path ->
Packit bd2e5d
      let frame = Frame.create tl in
Packit bd2e5d
      pack [frame] ~side:`Bottom ~fill:`X;
Packit bd2e5d
      add_shown_module path
Packit bd2e5d
        ~widgets:{ mw_frame = frame; mw_title = None; mw_detach = detach;
Packit bd2e5d
                   mw_edit = edit; mw_intf = intf }
Packit bd2e5d
  end
Packit bd2e5d
Packit bd2e5d
let choose_symbol_ref = ref choose_symbol
Packit bd2e5d
Packit bd2e5d
Packit bd2e5d
(* Search, both by type and name *)
Packit bd2e5d
Packit bd2e5d
let guess_search_mode s : [`Type | `Long | `Pattern] =
Packit bd2e5d
  let is_type = ref false and is_long = ref false in
Packit bd2e5d
  for i = 0 to String.length s - 2 do
Packit bd2e5d
    if s.[i] = '-' && s.[i+1] = '>' then is_type := true;
Packit bd2e5d
    if s.[i] = '.' then is_long := true
Packit bd2e5d
  done;
Packit bd2e5d
  if !is_type then `Type else if !is_long then `Long else `Pattern
Packit bd2e5d
Packit bd2e5d
Packit bd2e5d
let search_string ?(mode="symbol") ew =
Packit bd2e5d
  let text = Entry.get ew in
Packit bd2e5d
  try
Packit bd2e5d
    if text = "" then () else
Packit bd2e5d
    let l = match mode with
Packit bd2e5d
      "Name" ->
Packit bd2e5d
        begin match guess_search_mode text with
Packit bd2e5d
          `Long -> search_string_symbol text
Packit bd2e5d
        | `Pattern -> search_pattern_symbol text
Packit bd2e5d
        | `Type -> search_string_type text ~mode:`Included
Packit bd2e5d
        end
Packit bd2e5d
    | "Type" -> search_string_type text ~mode:`Included
Packit bd2e5d
    | "Exact" -> search_string_type text ~mode:`Exact
Packit bd2e5d
    | _ -> assert false
Packit bd2e5d
    in
Packit bd2e5d
    match l with [] -> ()
Packit bd2e5d
    | [lid,kind] -> view_symbol lid ~kind ~env:!start_env
Packit bd2e5d
    | l          -> choose_symbol ~title:"Choose symbol" ~env:!start_env l
Packit bd2e5d
  with Searchid.Error (s,e) ->
Packit bd2e5d
    Entry.icursor ew ~index:(`Num s)
Packit bd2e5d
Packit bd2e5d
let search_which = ref "Name"
Packit bd2e5d
Packit bd2e5d
let search_symbol () =
Packit bd2e5d
  if !module_list = [] then
Packit bd2e5d
  module_list := List.sort ~cmp:compare (list_modules ~path:!Config.load_path);
Packit bd2e5d
  let tl = Jg_toplevel.titled "Search symbol" in
Packit bd2e5d
  Jg_bind.escape_destroy tl;
Packit bd2e5d
  let ew = Entry.create tl ~width:30 in
Packit bd2e5d
  let choice = Frame.create tl
Packit bd2e5d
  and which = Textvariable.create ~on:tl () in
Packit bd2e5d
  let itself = Radiobutton.create choice ~text:"Itself"
Packit bd2e5d
        ~variable:which ~value:"Name"
Packit bd2e5d
  and extype = Radiobutton.create choice ~text:"Exact type"
Packit bd2e5d
        ~variable:which ~value:"Exact"
Packit bd2e5d
  and iotype = Radiobutton.create choice ~text:"Included type"
Packit bd2e5d
        ~variable:which ~value:"Type"
Packit bd2e5d
  and buttons = Frame.create tl in
Packit bd2e5d
  let search = Button.create buttons ~text:"Search" ~command:
Packit bd2e5d
    begin fun () ->
Packit bd2e5d
      search_which := Textvariable.get which;
Packit bd2e5d
      search_string ew ~mode:!search_which
Packit bd2e5d
    end
Packit bd2e5d
  and ok = Jg_button.create_destroyer tl ~parent:buttons ~text:"Cancel" in
Packit bd2e5d
Packit bd2e5d
  Focus.set ew;
Packit bd2e5d
  Jg_bind.return_invoke ew ~button:search;
Packit bd2e5d
  Textvariable.set which !search_which;
Packit bd2e5d
  pack [itself; extype; iotype] ~side:`Left ~anchor:`W;
Packit bd2e5d
  pack [search; ok] ~side:`Left ~fill:`X ~expand:true;
Packit bd2e5d
  pack [coe ew; coe choice; coe buttons]
Packit bd2e5d
       ~side:`Top ~fill:`X ~expand:true
Packit bd2e5d
Packit bd2e5d
Packit bd2e5d
(* Display the contents of a module *)
Packit bd2e5d
Packit bd2e5d
let ident_of_decl ~modlid = function
Packit bd2e5d
    Sig_value (id, _) -> Lident (Ident.name id), Pvalue
Packit bd2e5d
  | Sig_type (id, _, _) -> Lident (Ident.name id), Ptype
Packit bd2e5d
  | Sig_typext (id, _, _) -> Ldot (modlid, Ident.name id), Pconstructor
Packit bd2e5d
  | Sig_module (id, _, _) -> Lident (Ident.name id), Pmodule
Packit bd2e5d
  | Sig_modtype (id, _) -> Lident (Ident.name id), Pmodtype
Packit bd2e5d
  | Sig_class (id, _, _) -> Lident (Ident.name id), Pclass
Packit bd2e5d
  | Sig_class_type (id, _, _) -> Lident (Ident.name id), Pcltype
Packit bd2e5d
Packit bd2e5d
let view_defined ~env ?(show_all=false) modlid =
Packit bd2e5d
  try match Typetexp.find_module env Location.none modlid with
Packit bd2e5d
    path, {md_type=Mty_signature sign} ->
Packit bd2e5d
    let rec iter_sign sign idents =
Packit bd2e5d
      match sign with
Packit bd2e5d
        [] -> List.rev idents
Packit bd2e5d
      | decl :: rem ->
Packit bd2e5d
          let rem = match decl, rem with
Packit bd2e5d
            Sig_class _, cty :: ty1 :: ty2 :: rem -> rem
Packit bd2e5d
          | Sig_class_type _, ty1 :: ty2 :: rem -> rem
Packit bd2e5d
          | _, rem -> rem
Packit bd2e5d
          in iter_sign rem (ident_of_decl ~modlid decl :: idents)
Packit bd2e5d
    in
Packit bd2e5d
    let l = iter_sign sign [] in
Packit bd2e5d
    let title = string_of_path path in
Packit bd2e5d
    let env =
Packit bd2e5d
      match open_signature Asttypes.Fresh path env with None -> env
Packit bd2e5d
      | Some env -> env
Packit bd2e5d
    in
Packit bd2e5d
    !choose_symbol_ref l ~title ~signature:sign ~env ~path;
Packit bd2e5d
    if show_all then view_signature sign ~title ~env ~path
Packit bd2e5d
  | _ -> ()
Packit bd2e5d
  with Not_found -> ()
Packit bd2e5d
  | Env.Error err ->
Packit bd2e5d
      let tl, tw, finish = Jg_message.formatted ~title:"Error!" () in
Packit bd2e5d
      Env.report_error Format.std_formatter err;
Packit bd2e5d
      finish ()
Packit bd2e5d
  | Cmi_format.Error err ->
Packit bd2e5d
      let tl, tw, finish = Jg_message.formatted ~title:"Error!" () in
Packit bd2e5d
      Cmi_format.report_error Format.std_formatter err;
Packit bd2e5d
      finish ()
Packit bd2e5d
Packit bd2e5d
Packit bd2e5d
(* Manage toplevel windows *)
Packit bd2e5d
Packit bd2e5d
let close_all_views () =
Packit bd2e5d
    List.iter !top_widgets
Packit bd2e5d
      ~f:(fun tl -> try destroy tl with Protocol.TkError _ -> ());
Packit bd2e5d
    top_widgets := []
Packit bd2e5d
Packit bd2e5d
Packit bd2e5d
(* Launch a shell *)
Packit bd2e5d
Packit bd2e5d
let shell_counter = ref 1
Packit bd2e5d
let default_shell = ref "ocaml"
Packit bd2e5d
Packit bd2e5d
let start_shell master =
Packit bd2e5d
  let tl = Jg_toplevel.titled "Start New Shell" in
Packit bd2e5d
  Wm.transient_set tl ~master;
Packit bd2e5d
  let input = Frame.create tl
Packit bd2e5d
  and buttons = Frame.create tl in
Packit bd2e5d
  let ok = Button.create buttons ~text:"Ok"
Packit bd2e5d
  and cancel = Jg_button.create_destroyer tl ~parent:buttons ~text:"Cancel"
Packit bd2e5d
  and labels = Frame.create input
Packit bd2e5d
  and entries = Frame.create input in
Packit bd2e5d
  let l1 = Label.create labels ~text:"Command:"
Packit bd2e5d
  and l2 = Label.create labels ~text:"Title:"
Packit bd2e5d
  and e1 =
Packit bd2e5d
    Jg_entry.create entries ~command:(fun _ -> Button.invoke ok)
Packit bd2e5d
  and e2 =
Packit bd2e5d
    Jg_entry.create entries ~command:(fun _ -> Button.invoke ok)
Packit bd2e5d
  and names = List.map ~f:fst (Shell.get_all ()) in
Packit bd2e5d
  Entry.insert e1 ~index:`End ~text:!default_shell;
Packit bd2e5d
  let shell_name () = "Shell #" ^ string_of_int !shell_counter in
Packit bd2e5d
  while List.mem (shell_name ()) names do
Packit bd2e5d
    incr shell_counter
Packit bd2e5d
  done;
Packit bd2e5d
  Entry.insert e2 ~index:`End ~text:(shell_name ());
Packit bd2e5d
  Button.configure ok ~command:(fun () ->
Packit bd2e5d
      if not (List.mem (Entry.get e2) names) then begin
Packit bd2e5d
        default_shell := Entry.get e1;
Packit bd2e5d
        Shell.f ~prog:!default_shell ~title:(Entry.get e2);
Packit bd2e5d
        destroy tl
Packit bd2e5d
      end);
Packit bd2e5d
  pack [l1;l2] ~side:`Top ~anchor:`W;
Packit bd2e5d
  pack [e1;e2] ~side:`Top ~fill:`X ~expand:true;
Packit bd2e5d
  pack [labels;entries] ~side:`Left ~fill:`X ~expand:true;
Packit bd2e5d
  pack [ok;cancel] ~side:`Left ~fill:`X ~expand:true;
Packit bd2e5d
  pack [input;buttons] ~side:`Top ~fill:`X ~expand:true
Packit bd2e5d
Packit bd2e5d
Packit bd2e5d
(* Help window *)
Packit bd2e5d
Packit bd2e5d
let show_help () =
Packit bd2e5d
  let tl = Jg_toplevel.titled "OCamlBrowser Help" in
Packit bd2e5d
  Jg_bind.escape_destroy tl;
Packit bd2e5d
  let fw, tw, sb = Jg_text.create_with_scrollbar tl in
Packit bd2e5d
  let ok = Jg_button.create_destroyer ~parent:tl ~text:"Ok" tl in
Packit bd2e5d
  Text.insert tw ~index:tend ~text:Help.text;
Packit bd2e5d
  Text.configure tw ~state:`Disabled;
Packit bd2e5d
  Jg_bind.enter_focus tw;
Packit bd2e5d
  pack [tw] ~side:`Left ~fill:`Both ~expand:true;
Packit bd2e5d
  pack [sb] ~side:`Right ~fill:`Y;
Packit bd2e5d
  pack [fw] ~side:`Top ~expand:true ~fill:`Both;
Packit bd2e5d
  pack [ok] ~side:`Bottom ~fill:`X
Packit bd2e5d
Packit bd2e5d
(* Launch the classical viewer *)
Packit bd2e5d
Packit bd2e5d
let f ?(dir=Unix.getcwd()) ?on () =
Packit bd2e5d
  let (top, tl) = match on with
Packit bd2e5d
    None ->
Packit bd2e5d
      let tl = Jg_toplevel.titled "Module viewer" in
Packit bd2e5d
      ignore (Jg_bind.escape_destroy tl); (tl, coe tl)
Packit bd2e5d
  | Some top ->
Packit bd2e5d
      Wm.title_set top "OCamlBrowser";
Packit bd2e5d
      Wm.iconname_set top "OCamlBrowser";
Packit bd2e5d
      let tl = Frame.create top in
Packit bd2e5d
      bind tl ~events:[`Destroy] ~action:(fun _ -> exit 0);
Packit bd2e5d
      pack [tl] ~expand:true ~fill:`Both;
Packit bd2e5d
      (top, coe tl)
Packit bd2e5d
  in
Packit bd2e5d
  let menus = Jg_menu.menubar top in
Packit bd2e5d
  let filemenu = new Jg_menu.c "File" ~parent:menus
Packit bd2e5d
  and modmenu = new Jg_menu.c "Modules" ~parent:menus in
Packit bd2e5d
  let fmbox, mbox, msb = Jg_box.create_with_scrollbar tl in
Packit bd2e5d
Packit bd2e5d
  Jg_box.add_completion mbox ~nocase:true ~action:
Packit bd2e5d
    begin fun index ->
Packit bd2e5d
      view_defined (Lident (Listbox.get mbox ~index)) ~env:!start_env
Packit bd2e5d
    end;
Packit bd2e5d
  Setpath.add_update_hook (fun () -> reset_modules mbox);
Packit bd2e5d
Packit bd2e5d
  let ew = Entry.create tl in
Packit bd2e5d
  let buttons = Frame.create tl in
Packit bd2e5d
  let search = Button.create buttons ~text:"Search" ~pady:1
Packit bd2e5d
      ~command:(fun () -> search_string ew)
Packit bd2e5d
  and close =
Packit bd2e5d
    Button.create buttons ~text:"Close all" ~pady:1 ~command:close_all_views
Packit bd2e5d
  in
Packit bd2e5d
  (* bindings *)
Packit bd2e5d
  Jg_bind.enter_focus ew;
Packit bd2e5d
  Jg_bind.return_invoke ew ~button:search;
Packit bd2e5d
  bind close ~events:[`Modified([`Double], `ButtonPressDetail 1)]
Packit bd2e5d
    ~action:(fun _ -> destroy tl);
Packit bd2e5d
Packit bd2e5d
  (* File menu *)
Packit bd2e5d
  filemenu#add_command "Open..."
Packit bd2e5d
    ~command:(fun () -> !editor_ref ~opendialog:true ());
Packit bd2e5d
  filemenu#add_command "Editor..." ~command:(fun () -> !editor_ref ());
Packit bd2e5d
  filemenu#add_command "Shell..." ~command:(fun () -> start_shell tl);
Packit bd2e5d
  filemenu#add_command "Quit" ~command:(fun () -> destroy tl);
Packit bd2e5d
Packit bd2e5d
  (* modules menu *)
Packit bd2e5d
  modmenu#add_command "Path editor..."
Packit bd2e5d
    ~command:(fun () -> Setpath.set ~dir);
Packit bd2e5d
  modmenu#add_command "Reset cache"
Packit bd2e5d
    ~command:(fun () -> reset_modules mbox; Env.reset_cache ());
Packit bd2e5d
  modmenu#add_command "Search symbol..." ~command:search_symbol;
Packit bd2e5d
Packit bd2e5d
  pack [close; search] ~fill:`X ~side:`Right ~expand:true;
Packit bd2e5d
  pack [coe buttons; coe ew] ~fill:`X ~side:`Bottom;
Packit bd2e5d
  pack [msb] ~side:`Right ~fill:`Y;
Packit bd2e5d
  pack [mbox] ~side:`Left ~fill:`Both ~expand:true;
Packit bd2e5d
  pack [fmbox] ~fill:`Both ~expand:true ~side:`Top;
Packit bd2e5d
  reset_modules mbox
Packit bd2e5d
Packit bd2e5d
(* Smalltalk-like version *)
Packit bd2e5d
Packit bd2e5d
class st_viewer ?(dir=Unix.getcwd()) ?on () =
Packit bd2e5d
  let (top, tl) = match on with
Packit bd2e5d
    None ->
Packit bd2e5d
      let tl = Jg_toplevel.titled "Module viewer" in
Packit bd2e5d
      ignore (Jg_bind.escape_destroy tl); (tl, coe tl)
Packit bd2e5d
  | Some top ->
Packit bd2e5d
      Wm.title_set top "OCamlBrowser";
Packit bd2e5d
      Wm.iconname_set top "OCamlBrowser";
Packit bd2e5d
      let tl = Frame.create top in
Packit bd2e5d
      bind tl ~events:[`Destroy] ~action:(fun _ -> exit 0);
Packit bd2e5d
      pack [tl] ~side:`Bottom ~expand:true ~fill:`Both;
Packit bd2e5d
      (top, coe tl)
Packit bd2e5d
  in
Packit bd2e5d
  let menus = Menu.create top ~name:"menubar" ~typ:`Menubar in
Packit bd2e5d
  let () = Toplevel.configure top ~menu:menus in
Packit bd2e5d
  let filemenu = new Jg_menu.c "File" ~parent:menus
Packit bd2e5d
  and modmenu = new Jg_menu.c "Modules" ~parent:menus
Packit bd2e5d
  and viewmenu = new Jg_menu.c "View" ~parent:menus
Packit bd2e5d
  and helpmenu = new Jg_menu.c "Help" ~parent:menus in
Packit bd2e5d
  let search_frame = Frame.create tl in
Packit bd2e5d
  let boxes_frame = Frame.create tl ~name:"boxes" in
Packit bd2e5d
  let label = Label.create tl ~anchor:`W ~padx:5 in
Packit bd2e5d
  let view = Frame.create tl in
Packit bd2e5d
  let buttons = Frame.create tl in
Packit bd2e5d
  let _all = Button.create buttons ~text:"Show all" ~padx:20
Packit bd2e5d
  and close = Button.create buttons ~text:"Close all" ~command:close_all_views
Packit bd2e5d
  and detach = Button.create buttons ~text:"Detach"
Packit bd2e5d
  and edit = Button.create buttons ~text:"Impl"
Packit bd2e5d
  and intf = Button.create buttons ~text:"Intf" in
Packit bd2e5d
object (self)
Packit bd2e5d
  val mutable boxes = []
Packit bd2e5d
  val mutable show_all = fun () -> ()
Packit bd2e5d
Packit bd2e5d
  method create_box =
Packit bd2e5d
    let fmbox, mbox, sb = Jg_box.create_with_scrollbar boxes_frame in
Packit bd2e5d
    bind mbox ~events:[`Modified([`Double], `ButtonPressDetail 1)]
Packit bd2e5d
      ~action:(fun _ -> show_all ());
Packit bd2e5d
    bind mbox ~events:[`Modified([`Double], `KeyPressDetail "Return")]
Packit bd2e5d
      ~action:(fun _ -> show_all ());
Packit bd2e5d
    boxes <- boxes @ [fmbox, mbox];
Packit bd2e5d
    pack [sb] ~side:`Right ~fill:`Y;
Packit bd2e5d
    pack [mbox] ~side:`Left ~fill:`Both ~expand:true;
Packit bd2e5d
    pack [fmbox] ~side:`Left ~fill:`Both ~expand:true;
Packit bd2e5d
    fmbox, mbox
Packit bd2e5d
Packit bd2e5d
  initializer
Packit bd2e5d
    (* Search *)
Packit bd2e5d
    let ew = Entry.create search_frame
Packit bd2e5d
    and searchtype = Textvariable.create ~on:tl () in
Packit bd2e5d
    bind ew ~events:[`KeyPressDetail "Return"] ~action:
Packit bd2e5d
      (fun _ -> search_string ew ~mode:(Textvariable.get searchtype));
Packit bd2e5d
    Jg_bind.enter_focus ew;
Packit bd2e5d
    let search_button ?value text =
Packit bd2e5d
      Radiobutton.create search_frame
Packit bd2e5d
        ~text ~variable:searchtype ~value:text in
Packit bd2e5d
    let symbol = search_button "Name"
Packit bd2e5d
    and atype = search_button "Type" in
Packit bd2e5d
    Radiobutton.select symbol;
Packit bd2e5d
    pack [Label.create search_frame ~text:"Search"] ~side:`Left ~ipadx:5;
Packit bd2e5d
    pack [ew] ~fill:`X ~expand:true ~side:`Left;
Packit bd2e5d
    pack [Label.create search_frame ~text:"by"] ~side:`Left ~ipadx:5;
Packit bd2e5d
    pack [symbol; atype] ~side:`Left;
Packit bd2e5d
    pack [Label.create search_frame] ~side:`Right
Packit bd2e5d
Packit bd2e5d
  initializer
Packit bd2e5d
    (* Boxes *)
Packit bd2e5d
    let fmbox, mbox = self#create_box in
Packit bd2e5d
    Jg_box.add_completion mbox ~nocase:true ~double:false ~action:
Packit bd2e5d
      begin fun index ->
Packit bd2e5d
        view_defined (Lident (Listbox.get mbox ~index)) ~env:!start_env
Packit bd2e5d
      end;
Packit bd2e5d
    Setpath.add_update_hook (fun () -> reset_modules mbox; self#hide_after 1);
Packit bd2e5d
    List.iter [1;2] ~f:(fun _ -> ignore self#create_box);
Packit bd2e5d
    Searchpos.default_frame := Some
Packit bd2e5d
        { mw_frame = view; mw_title = Some label;
Packit bd2e5d
          mw_detach = detach; mw_edit = edit; mw_intf = intf };
Packit bd2e5d
    Searchpos.set_path := self#set_path;
Packit bd2e5d
Packit bd2e5d
    (* Buttons *)
Packit bd2e5d
    pack [close] ~side:`Right ~fill:`X ~expand:true;
Packit bd2e5d
    bind close ~events:[`Modified([`Double], `ButtonPressDetail 1)]
Packit bd2e5d
      ~action:(fun _ -> destroy tl);
Packit bd2e5d
Packit bd2e5d
    (* File menu *)
Packit bd2e5d
    filemenu#add_command "Open..."
Packit bd2e5d
      ~command:(fun () -> !editor_ref ~opendialog:true ());
Packit bd2e5d
    filemenu#add_command "Editor..." ~command:(fun () -> !editor_ref ());
Packit bd2e5d
    filemenu#add_command "Shell..." ~command:(fun () -> start_shell tl);
Packit bd2e5d
    filemenu#add_command "Quit" ~command:(fun () -> destroy tl);
Packit bd2e5d
Packit bd2e5d
    (* View menu *)
Packit bd2e5d
    viewmenu#add_command "Show all defs" ~command:(fun () -> show_all ());
Packit bd2e5d
    let show_search = Textvariable.create ~on:tl () in
Packit bd2e5d
    Textvariable.set show_search "1";
Packit bd2e5d
    Menu.add_checkbutton viewmenu#menu ~label:"Search Entry"
Packit bd2e5d
      ~variable:show_search ~indicatoron:true ~state:`Active
Packit bd2e5d
      ~command:
Packit bd2e5d
      begin fun () ->
Packit bd2e5d
        let v = Textvariable.get show_search in
Packit bd2e5d
        if v = "1" then begin
Packit bd2e5d
          pack [search_frame] ~after:menus ~fill:`X
Packit bd2e5d
        end else Pack.forget [search_frame]
Packit bd2e5d
      end;
Packit bd2e5d
Packit bd2e5d
    (* modules menu *)
Packit bd2e5d
    modmenu#add_command "Path editor..."
Packit bd2e5d
      ~command:(fun () -> Setpath.set ~dir);
Packit bd2e5d
    modmenu#add_command "Reset cache"
Packit bd2e5d
      ~command:(fun () -> reset_modules mbox; Env.reset_cache ());
Packit bd2e5d
    modmenu#add_command "Search symbol..." ~command:search_symbol;
Packit bd2e5d
Packit bd2e5d
    (* Help menu *)
Packit bd2e5d
    helpmenu#add_command "Manual..." ~command:show_help;
Packit bd2e5d
Packit bd2e5d
    pack [search_frame] ~fill:`X;
Packit bd2e5d
    pack [boxes_frame] ~fill:`Both ~expand:true;
Packit bd2e5d
    pack [buttons] ~fill:`X ~side:`Bottom;
Packit bd2e5d
    pack [view] ~fill:`Both ~side:`Bottom ~expand:true;
Packit bd2e5d
    reset_modules mbox
Packit bd2e5d
Packit bd2e5d
  val mutable shown_paths = []
Packit bd2e5d
Packit bd2e5d
  method hide_after n =
Packit bd2e5d
    for i = n to List.length boxes - 1 do
Packit bd2e5d
      let fm, box = List.nth boxes i in
Packit bd2e5d
      if i < 3 then Listbox.delete box ~first:(`Num 0) ~last:`End
Packit bd2e5d
      else destroy fm
Packit bd2e5d
    done;
Packit bd2e5d
    let rec firsts n = function [] -> []
Packit bd2e5d
      | a :: l -> if n > 0 then a :: firsts (pred n) l else [] in
Packit bd2e5d
    shown_paths <- firsts (n-1) shown_paths;
Packit bd2e5d
    boxes <- firsts (max 3 n) boxes
Packit bd2e5d
Packit bd2e5d
  method get_box ~path =
Packit bd2e5d
    let rec path_index p = function
Packit bd2e5d
        [] -> raise Not_found
Packit bd2e5d
      | a :: l -> if Path.same p a then 1 else path_index p l + 1 in
Packit bd2e5d
    try
Packit bd2e5d
      let n = path_index path shown_paths in
Packit bd2e5d
      self#hide_after (n+1);
Packit bd2e5d
      n
Packit bd2e5d
    with Not_found ->
Packit bd2e5d
      match path with
Packit bd2e5d
        Path.Pdot (path', _, _) ->
Packit bd2e5d
          let n = self#get_box ~path:path' in
Packit bd2e5d
          shown_paths <- shown_paths @ [path];
Packit bd2e5d
          if n + 1 >= List.length boxes then ignore self#create_box;
Packit bd2e5d
          n+1
Packit bd2e5d
      | _ ->
Packit bd2e5d
          self#hide_after 2;
Packit bd2e5d
          shown_paths <- [path];
Packit bd2e5d
          1
Packit bd2e5d
Packit bd2e5d
  method set_path path ~sign =
Packit bd2e5d
    let rec path_elems l path =
Packit bd2e5d
      match path with
Packit bd2e5d
        Path.Pdot (path, _, _) -> path_elems (path::l) path
Packit bd2e5d
      | _ -> []
Packit bd2e5d
    in
Packit bd2e5d
    let path_elems path =
Packit bd2e5d
      match path with
Packit bd2e5d
      | Path.Pident _ -> [path]
Packit bd2e5d
      | _ -> path_elems [] path
Packit bd2e5d
    in
Packit bd2e5d
    let see_path ~box:n ?(sign=[]) path =
Packit bd2e5d
      let (_, box) = List.nth boxes n in
Packit bd2e5d
      let texts = Listbox.get_range box ~first:(`Num 0) ~last:`End in
Packit bd2e5d
      let rec index s = function
Packit bd2e5d
          [] -> raise Not_found
Packit bd2e5d
        | a :: l -> if a = s then 0 else 1 + index s l
Packit bd2e5d
      in
Packit bd2e5d
      try
Packit bd2e5d
        let modlid, s =
Packit bd2e5d
          match path with
Packit bd2e5d
            Path.Pdot (p, s, _) -> longident_of_path p, s
Packit bd2e5d
          | Path.Pident i -> Longident.Lident "M", Ident.name i
Packit bd2e5d
          | _ -> assert false
Packit bd2e5d
        in
Packit bd2e5d
        let li, k =
Packit bd2e5d
          if sign = [] then Longident.Lident s, Pmodule else
Packit bd2e5d
          ident_of_decl ~modlid (List.hd sign) in
Packit bd2e5d
        let s =
Packit bd2e5d
          if n = 0 then string_of_longident li else
Packit bd2e5d
          string_of_longident li ^ " (" ^ string_of_kind k ^ ")" in
Packit bd2e5d
        let n = index s texts in
Packit bd2e5d
        Listbox.see box (`Num n);
Packit bd2e5d
        Listbox.activate box (`Num n)
Packit bd2e5d
      with Not_found -> ()
Packit bd2e5d
    in
Packit bd2e5d
    let l = path_elems path in
Packit bd2e5d
    if l <> [] then begin
Packit bd2e5d
      List.iter l ~f:
Packit bd2e5d
        begin fun path ->
Packit bd2e5d
          if not (List.mem path shown_paths) then
Packit bd2e5d
            view_symbol (longident_of_path path) ~kind:Pmodule
Packit bd2e5d
              ~env:!start_env ~path;
Packit bd2e5d
          let n = self#get_box path - 1 in
Packit bd2e5d
          see_path path ~box:n
Packit bd2e5d
        end;
Packit bd2e5d
      see_path path ~box:(self#get_box path) ~sign
Packit bd2e5d
    end
Packit bd2e5d
Packit bd2e5d
  method choose_symbol ~title ~env ?signature ?path l =
Packit bd2e5d
    let n =
Packit bd2e5d
      match path with None -> 1
Packit bd2e5d
      | Some path -> self#get_box ~path
Packit bd2e5d
    in
Packit bd2e5d
    let l = List.sort l ~cmp:(fun (li1, _) (li2,_) -> compare li1 li2) in
Packit bd2e5d
    let nl = List.map l ~f:
Packit bd2e5d
        begin fun (li, k) ->
Packit bd2e5d
          string_of_longident li ^ " (" ^ string_of_kind k ^ ")"
Packit bd2e5d
        end in
Packit bd2e5d
    let _, box = List.nth boxes n in
Packit bd2e5d
    Listbox.delete box ~first:(`Num 0) ~last:`End;
Packit bd2e5d
    Listbox.insert box ~index:`End ~texts:nl;
Packit bd2e5d
Packit bd2e5d
    let current = ref None in
Packit bd2e5d
    let display index =
Packit bd2e5d
      let `Num pos = Listbox.index box ~index in
Packit bd2e5d
      try
Packit bd2e5d
        let li, k = try List.nth l pos with Failure _ -> raise Exit in
Packit bd2e5d
        self#hide_after (n+1);
Packit bd2e5d
        if !current = Some (li,k) then () else
Packit bd2e5d
        let path =
Packit bd2e5d
          match path, li with
Packit bd2e5d
            None, Ldot (lip, _) ->
Packit bd2e5d
              begin try
Packit bd2e5d
                Some (lookup_module ~load:true lip env)
Packit bd2e5d
              with Not_found -> None
Packit bd2e5d
              end
Packit bd2e5d
          | _ -> path
Packit bd2e5d
        in
Packit bd2e5d
        current := Some (li,k);
Packit bd2e5d
        view_symbol li ~kind:k ~env ?path
Packit bd2e5d
      with Exit -> ()
Packit bd2e5d
    in
Packit bd2e5d
    Jg_box.add_completion box ~double:false ~action:display;
Packit bd2e5d
    bind box ~events:[`KeyRelease] ~fields:[`Char]
Packit bd2e5d
      ~action:(fun ev -> display `Active);
Packit bd2e5d
Packit bd2e5d
    begin match signature with
Packit bd2e5d
      None -> ()
Packit bd2e5d
    | Some signature ->
Packit bd2e5d
        show_all <-
Packit bd2e5d
          begin fun () ->
Packit bd2e5d
            current := None;
Packit bd2e5d
            view_signature signature ~title ~env ?path
Packit bd2e5d
          end
Packit bd2e5d
    end
Packit bd2e5d
end
Packit bd2e5d
Packit bd2e5d
let st_viewer ?dir ?on () =
Packit bd2e5d
  let viewer = new st_viewer ?dir ?on () in
Packit bd2e5d
  choose_symbol_ref := viewer#choose_symbol