why3ide.ml 14.3 KB
Newer Older
1 2 3 4

open Format
open Why3
open Gconfig
5 6 7
open Stdlib
open Session_itp
open Controller_itp
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45

let debug = Debug.lookup_flag "ide_info"

(************************)
(* parsing command line *)
(************************)

let files = Queue.create ()
let opt_parser = ref None

let spec = Arg.align [
  "-F", Arg.String (fun s -> opt_parser := Some s),
      "<format> select input format (default: \"why\")";
  "--format", Arg.String (fun s -> opt_parser := Some s),
      " same as -F";
(*
  "-f",
   Arg.String (fun s -> input_files := s :: !input_files),
   "<file> add file to the project (ignored if it is already there)";
*)
  Termcode.arg_extra_expl_prefix
]

let usage_str = sprintf
  "Usage: %s [options] [<file.why>|<project directory>]..."
  (Filename.basename Sys.argv.(0))

let gconfig = try
  let config, base_config, env =
    Whyconf.Args.initialize spec (fun f -> Queue.add f files) usage_str in
  if Queue.is_empty files then Whyconf.Args.exit_with_usage spec usage_str;
  Gconfig.load_config config base_config env;
  Gconfig.config ()

  with e when not (Debug.test_flag Debug.stack_trace) ->
    eprintf "%a@." Exn_printer.exn_printer e;
    exit 1

46 47 48 49 50 51 52 53
let task_driver =
  let main = Whyconf.get_main gconfig.config in
  let d = Filename.concat (Whyconf.datadir main)
                          (Filename.concat "drivers" "why3_itp.drv")
  in
  Driver.load_driver gconfig.env d []


MARCHE Claude's avatar
MARCHE Claude committed
54 55 56 57 58 59
let provers : Whyconf.config_prover Whyconf.Mprover.t =
  Whyconf.get_provers gconfig.config

let cont =
  Session_user_interface.cont_from_files spec usage_str gconfig.env files provers

60 61 62 63 64 65
let () =
  Debug.dprintf debug "[GUI] Init the GTK interface...@?";
  ignore (GtkMain.Main.init ());
  Debug.dprintf debug " done.@.";
  Gconfig.init ()

MARCHE Claude's avatar
MARCHE Claude committed
66

67 68 69
(**********************)
(* Graphical elements *)
(**********************)
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123

let main_window =
  let w = GWindow.window
            ~allow_grow:true ~allow_shrink:true
            ~width:gconfig.window_width
            ~height:gconfig.window_height
            ~title:"Why3 Interactive Proof Session" ()
  in
  (* callback to record the new size of the main window when changed, so
   that on restart the window size is the same as the last session *)
  let (_ : GtkSignal.id) =
    w#misc#connect#size_allocate
      ~callback:
      (fun {Gtk.width=w;Gtk.height=h} ->
       gconfig.window_height <- h;
       gconfig.window_width <- w)
  in w

(* the main window contains a vertical box, containing:
   1. the menu
   2. an horizontal box
 *)

let vbox = GPack.vbox ~packing:main_window#add ()

let menubar = GMenu.menu_bar
  ~packing:(vbox#pack ?from:None ?expand:None ?fill:None ?padding:None)
  ()

let hb = GPack.hbox ~packing:vbox#add ()

(* 1. Menu *)

let factory = new GMenu.factory menubar

let accel_group = factory#accel_group

let file_menu = factory#add_submenu "_File"
let file_factory = new GMenu.factory file_menu ~accel_group

let exit_function ~destroy () =
  ignore(destroy); GMain.quit ()

let (_ : GtkSignal.id) = main_window#connect#destroy
  ~callback:(exit_function ~destroy:true)

let (_ : GMenu.menu_item) =
  file_factory#add_item ~key:GdkKeysyms._Q "_Quit"
    ~callback:(exit_function ~destroy:false)

(* 2. horizontal box contains:
   2.1 TODO: a tool box ?
   2.2 a horizontal paned containing:
     2.2.1 a scrolled window to hold the tree view of the session
Clément Fumex's avatar
Clément Fumex committed
124
     2.2.2 a vertical paned containing
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
*)

let hp = GPack.paned `HORIZONTAL ~packing:hb#add ()

let scrollview =
  let sv =
    GBin.scrolled_window
      ~hpolicy:`AUTOMATIC ~vpolicy:`AUTOMATIC
      ~width:gconfig.tree_width ~shadow_type:`ETCHED_OUT
      ~packing:hp#add ()
  in
  let (_ : GtkSignal.id) =
    sv#misc#connect#size_allocate
      ~callback:
      (fun {Gtk.width=w;Gtk.height=_h} ->
       gconfig.tree_width <- w)
  in sv

Clément Fumex's avatar
Clément Fumex committed
143
let vpan222 = GPack.paned `VERTICAL ~packing:hp#add ()
144 145 146 147 148 149

(*  the scrolled window 2.2.1 contains a GTK tree

*)


MARCHE Claude's avatar
MARCHE Claude committed
150 151 152 153 154 155 156 157
(* view for the session tree *)
let scrolled_session_view =
  GBin.scrolled_window
    ~hpolicy: `AUTOMATIC ~vpolicy: `AUTOMATIC
    ~shadow_type:`ETCHED_OUT
    ~packing:scrollview#add
    ()

158 159 160
let cols = new GTree.column_list
let name_column = cols#add Gobject.Data.string
let index_column = cols#add Gobject.Data.int
MARCHE Claude's avatar
MARCHE Claude committed
161

162 163 164 165 166
let name_renderer = GTree.cell_renderer_text [`XALIGN 0.]
let view_name_column = GTree.view_column ~title:"Theories/Goals" ()
let () =
  view_name_column#pack name_renderer;
  view_name_column#add_attribute name_renderer "text" name_column
167

168 169 170 171 172 173 174 175 176 177 178 179 180 181 182
let goals_model,goals_view =
  Debug.dprintf debug "[GUI] Creating tree model...@?";
  let model = GTree.tree_store cols in
  let view = GTree.view ~model ~packing:scrolled_session_view#add () in
(*
  let () = view#selection#set_mode (* `SINGLE *) `MULTIPLE in
  let () = view#set_rules_hint true in
 *)
  ignore (view#append_column view_name_column);
(*
  ignore (view#append_column view_status_column);
  ignore (view#append_column view_time_column);
*)
  Debug.dprintf debug " done@.";
  model,view
183

Clément Fumex's avatar
Clément Fumex committed
184
(* vpan222 contains:
185
   2.2.2.1 a view of the current task
Clément Fumex's avatar
Clément Fumex committed
186 187 188
   2.2.2.2 a vertiacal pan which contains
     2.2.2.2.1 the input field to type commands
     2.2.2.2.2 the output of the commands
189 190 191 192 193 194
 *)

let scrolled_task_view =
  GBin.scrolled_window
    ~hpolicy: `AUTOMATIC ~vpolicy: `AUTOMATIC
    ~shadow_type:`ETCHED_OUT
Clément Fumex's avatar
Clément Fumex committed
195
    ~packing:vpan222#add ()
196 197 198 199

let task_view =
  GSourceView2.source_view
    ~editable:false
Clément Fumex's avatar
Clément Fumex committed
200
    ~cursor_visible:false
201 202 203 204
    ~show_line_numbers:true
    ~packing:scrolled_task_view#add
    ()

Clément Fumex's avatar
Clément Fumex committed
205
let vbox2222 = GPack.vbox ~packing:vpan222#add  ()
206

Clément Fumex's avatar
Clément Fumex committed
207 208
let command_entry =
  GEdit.entry ~packing:(vbox2222#pack ?from:None ?expand:None ?fill:None ?padding:None) ()
MARCHE Claude's avatar
MARCHE Claude committed
209 210
let message_zone =
  GText.view ~editable:false ~cursor_visible:false
Clément Fumex's avatar
Clément Fumex committed
211
             ~packing:(vbox2222#pack ?from:None ~expand:true ~fill:true ?padding:None) ()
MARCHE Claude's avatar
MARCHE Claude committed
212

213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235
(********************************************)
(* controller instance on the GTK scheduler *)
(********************************************)


module S = struct
    let idle ~prio f =
      let (_ : GMain.Idle.id) = GMain.Idle.add ~prio f in ()

    let timeout ~ms f =
      let (_ : GMain.Timeout.id) = GMain.Timeout.add ~ms ~callback:f in
      ()
end

module C = Controller_itp.Make(S)


(***********************************)
(* Mapping session to the GTK tree *)
(***********************************)

type index =
  | Inone
236
  | IproofAttempt of proofAttemptID
237
  | IproofNode of proofNodeID
238
  | Itransformation  of transID
239 240 241 242
  | Ifile of file
  | Itheory of theory

let model_index : index Hint.t = Stdlib.Hint.create 17
243 244 245 246
(* To each proofnodeid we have the corresponding row_reference *)
let pn_id_to_gtree : GTree.row_reference Hpn.t = Hpn.create 17
let pan_id_to_gtree : GTree.row_reference Hpan.t = Hpan.create 17

247 248 249

let new_node =
  let cpt = ref (-1) in
250
  fun ?parent ?(collapse=false) name ind ->
251 252 253 254 255 256
  incr cpt;
  Hint.add model_index !cpt ind;
  let parent = Opt.map (fun x -> x#iter) parent in
  let iter = goals_model#append ?parent () in
  goals_model#set ~row:iter ~column:name_column name;
  goals_model#set ~row:iter ~column:index_column !cpt;
257
  let new_ref = goals_model#get_row_reference (goals_model#get_path iter) in
258 259
  (* By default expand_path when creating a new node *)
  if not collapse then goals_view#expand_to_path (goals_model#get_path iter);
260 261 262 263 264 265 266 267 268
  begin
    match ind with
    | IproofAttempt panid ->
       Hpan.add pan_id_to_gtree panid new_ref
    | IproofNode pnid ->
       Hpn.add pn_id_to_gtree pnid new_ref
    | _ -> ()
  end;
  new_ref
269

270
let build_subtree_proof_attempt_from_goal ses row_ref id =
271 272 273 274 275 276
  Whyconf.Hprover.iter
    (fun pa panid ->
     let name = Pp.string_of Whyconf.print_prover pa in
      ignore(new_node ~parent:row_ref name
                 (IproofAttempt panid)))
    (get_proof_attempt_ids ses id)
277 278

let rec build_subtree_from_goal ses th_row_reference id =
279
  let name = get_proof_name ses id in
280
  let row_ref =
281 282
    new_node ~parent:th_row_reference name.Ident.id_string
             (IproofNode id)
MARCHE Claude's avatar
MARCHE Claude committed
283
  in
284 285 286 287 288 289 290 291 292 293 294 295 296 297
  List.iter
    (fun trans_id ->
      build_subtree_from_trans ses row_ref trans_id)
    (get_transformations ses id);
  build_subtree_proof_attempt_from_goal ses row_ref id

and build_subtree_from_trans ses goal_row_reference trans_id =
  let name = get_transf_name ses trans_id in
  let row_ref =
    new_node ~parent:goal_row_reference name (Itransformation trans_id) in
  List.iter
    (fun goal_id ->
      (build_subtree_from_goal ses row_ref goal_id))
    (get_sub_tasks ses trans_id)
298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318

let build_tree_from_session ses =
  let files = get_files ses in
  Stdlib.Hstr.iter
    (fun _ file ->
       let file_row_reference = new_node file.file_name (Ifile file) in
       List.iter (fun th ->
                  let th_row_reference =
                    new_node ~parent:file_row_reference
                             (theory_name th).Ident.id_string
                             (Itheory th)
                  in
                  List.iter (build_subtree_from_goal ses th_row_reference)
                            (theory_goals th))
                 file.file_theories)
    files

(******************)
(*    actions     *)
(******************)

319 320
(* TODO We currently use this for transformations etc... With strategies, we sure
   do not want to move the current index with the computing of strategy. *)
321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342
let current_selected_index = ref Inone

let run_strategy_on_task s =
  match !current_selected_index with
  | IproofNode id ->
     let l = Session_user_interface.strategies
               cont.controller_env gconfig.config
     in
     let st = List.filter (fun (_,c,_,_) -> c=s) l in
     begin
       match st with
       | [(n,_,_,st)] ->
          printf "running strategy '%s'@." n;
          let callback sts =
            printf "Strategy status: %a@." print_strategy_status sts
          in
          C.run_strategy_on_goal cont id st ~callback
    | _ -> printf "Strategy '%s' not found@." s
     end
  | _ -> ()


343 344
(* TODO maybe an other file for callbacks *)
(* Callback of a transformation *)
345
let callback_update_tree_transform ses row_reference = fun status ->
346 347
  match status with
  | TSdone trans_id ->
348 349 350 351 352 353
      build_subtree_from_trans ses row_reference trans_id;
      (match Session_itp.get_sub_tasks ses trans_id with
      | first_goal :: _ ->
          (* Put the selection on the first goal *)
          goals_view#selection#select_iter (Hpn.find pn_id_to_gtree first_goal)#iter
      | [] -> ())
354 355
  | _ -> ()

356
let apply_transform ses t args =
357 358
  match !current_selected_index with
  | IproofNode id ->
359
    let row_ref = Hpn.find pn_id_to_gtree id in (* TODO exception *)
360
    let callback =
361
         callback_update_tree_transform ses row_ref
362
       in
363
       C.schedule_transformation cont id t args ~callback
364 365 366 367 368 369 370 371 372
    | _ -> printf "Error: Give the name of the transformation@."


let remove_children iter =
  if (goals_model#iter_has_child iter) then
    ignore (goals_model#remove (goals_model#iter_children (Some iter)))
  else ()

(* Callback of a proof_attempt *)
373 374
let callback_update_tree_proof _ses row_ref _id name =
  fun panid pa_status ->
375
  match pa_status with
376 377 378 379 380 381 382 383
    | Scheduled ->
       begin
       try
         let _new_row_ref = Hpan.find pan_id_to_gtree panid in
         () (* TODO: set icon to 'pause' *)
       with Not_found ->
            ignore(new_node ~parent:row_ref (name ^ " scheduled") (IproofAttempt panid))
       end
384
  | Done _pr ->
385 386 387 388 389 390 391 392
       begin
       try
         let r = Hpan.find pan_id_to_gtree panid in
         goals_model#set ~row:r#iter ~column:name_column (name ^ " done")
       with Not_found -> assert false
       end
  | Running -> () (* TODO: set icon to 'play' *)
  | _ ->  () (* TODO ? *)
393

394
let test_schedule_proof_attempt ses (p: Whyconf.config_prover) limit =
395 396
  match !current_selected_index with
  | IproofNode id ->
397
    let row_ref = Hpn.find pn_id_to_gtree id in
398 399 400 401 402 403 404
    let prover = p.Whyconf.prover in
    let printing = prover.Whyconf.prover_name ^ "(" ^ prover.Whyconf.prover_version ^ ")" in
    let callback = callback_update_tree_proof ses row_ref id printing in
        C.schedule_proof_attempt
          cont id prover
          ~limit ~callback
  | _ -> message_zone#buffer#set_text ("Must be on a proof node to use a prover.")
405 406

let clear_command_entry () = command_entry#set_text ""
MARCHE Claude's avatar
MARCHE Claude committed
407

408
open Session_user_interface
MARCHE Claude's avatar
MARCHE Claude committed
409 410

let interp cmd =
411 412 413 414 415 416
  match interp cont.controller_env cmd with
    | Transform(s,_t,args) ->
       clear_command_entry ();
       apply_transform cont.controller_session s args
    | Query s ->
       message_zone#buffer#set_text s
417 418 419 420 421 422 423 424 425 426 427 428
    | Other(s,args) ->
      begin
        match parse_prover_name gconfig.config s args with
        | Some (prover_config, limit) ->
          clear_command_entry ();
          test_schedule_proof_attempt cont.controller_session prover_config limit
        | None ->
          match s with
          | "s" -> clear_command_entry ();
                   run_strategy_on_task "1"
          | _ -> message_zone#buffer#set_text ("unknown command '"^s^"'")
      end
MARCHE Claude's avatar
MARCHE Claude committed
429 430 431 432

let (_ : GtkSignal.id) =
  command_entry#connect#activate
    ~callback:(fun () -> interp command_entry#text)
433 434


435 436 437 438 439 440 441 442
let get_selected_row_references () =
  List.map
    (fun path -> goals_model#get_row_reference path)
    goals_view#selection#get_selected_rows

let on_selected_row r =
  let index = goals_model#get ~row:r#iter ~column:index_column in
  try
443 444
    let session_element = Hint.find model_index index in
    let () = match session_element with
445
             | IproofNode id -> Hpn.add pn_id_to_gtree id r (* TODO maybe not the good place to fill
446 447 448 449
                                                                    this table *)
             | _ -> () in
    current_selected_index := session_element;
    match session_element with
450 451 452 453 454
    | IproofNode id ->
       let task = get_task cont.controller_session id in
       let s = Pp.string_of
                 (Driver.print_task ~cntexample:false task_driver)
                 task
Clément Fumex's avatar
Clément Fumex committed
455 456 457
       in task_view#source_buffer#set_text s;
       (* scroll to end of text *)
       task_view#scroll_to_mark `INSERT
458 459 460 461 462 463 464 465 466 467 468 469
    | _ -> task_view#source_buffer#set_text ""
  with
    | Not_found -> task_view#source_buffer#set_text ""

let (_ : GtkSignal.id) =
  goals_view#selection#connect#after#changed ~callback:
    (fun () ->
      match get_selected_row_references () with
        | [r] -> on_selected_row r
        | _ -> ())

(***********************)
470
(* start the interface *)
471
(***********************)
472 473

let () =
474 475
  build_tree_from_session cont.controller_session;
  (* temporary *)
Clément Fumex's avatar
Clément Fumex committed
476
  vpan222#set_position 500;
477
  goals_view#expand_all ();
478 479 480 481
  main_window#add_accel_group accel_group;
  main_window#set_icon (Some !Gconfig.why_icon);
  main_window#show ();
  GMain.main ()