open Vernacexpr
open Coq
open Ideutils
let out_some s = match s with | None -> assert false | Some f -> f
let yes_icon = "gtk-yes"
let no_icon = "gtk-no"
let save_icon = "gtk-save"
let saveas_icon = "gtk-save-as"
let window_width = 800
let window_height = 600
let initial_cwd = Sys.getcwd ()
let default_general_font_name = "Sans 14"
let default_monospace_font_name = "Monospace 14"
let manual_monospace_font = ref None
let manual_general_font = ref None
let has_config_file = (Sys.file_exists ".coqiderc") ||
(try Sys.file_exists
(Filename.concat (Sys.getenv "HOME") ".coqiderc")
with Not_found -> false)
let _ = if not has_config_file then
manual_monospace_font := Some (Pango.Font.from_string default_monospace_font_name);
manual_general_font := Some (Pango.Font.from_string default_general_font_name)
let (font_selector:GWindow.font_selection_dialog option ref) = ref None
let (message_view:GText.view option ref) = ref None
let (proof_view:GText.view option ref) = ref None
let (_notebook:GPack.notebook option ref) = ref None
let notebook () = out_some !_notebook
let decompose_tab w =
let vbox = new GPack.box ((Gobject.try_cast w "GtkBox"):Gtk.box Gtk.obj) in
let l = vbox#children in
match l with
| [img;lbl] ->
let img = new GMisc.image
((Gobject.try_cast img#as_widget "GtkImage"):
Gtk.image Gtk.obj)
in
let lbl = GMisc.label_cast lbl in
vbox,img,lbl
| _ -> assert false
let set_tab_label i n =
let nb = notebook () in
let _,_,lbl = decompose_tab (nb#get_tab_label(nb#get_nth_page i))#as_widget in
lbl#set_markup n
let set_tab_image i s =
let nb = notebook () in
let _,img,_ = decompose_tab (nb#get_tab_label(nb#get_nth_page i))#as_widget in
img#set_stock s ~size:1
let set_current_tab_image s = set_tab_image (notebook())#current_page s
let set_current_tab_label n =
set_tab_label (notebook())#current_page n
let get_tab_label i =
let nb = notebook () in
let _,_,lbl = decompose_tab (nb#get_tab_label(nb#get_nth_page i))#as_widget in
lbl#text
let get_current_tab_label () = get_tab_label (notebook())#current_page
let reset_tab_label i = set_tab_label i (get_tab_label i)
module Vector = struct
type 'a t = 'a array ref
let create () = ref [||]
let get t = Array.get !t
let set t = Array.set !t
let append t e = t := Array.append !t [|e|]; (Array.length !t)-1
let iter f t = Array.iter f !t
let exists f t =
let l = Array.length !t in
let rec test i = i < l && (f !t.(i) || test (i+1)) in
test 0
end
type 'a viewable_script =
{view : GText.view;
mutable analyzed_view : 'a option;
mutable filename : string option
}
class type analyzed_views =
object('self)
val mutable act_id : GtkSignal.id option
val current_all : 'self viewable_script
val mutable deact_id : GtkSignal.id option
val input_buffer : GText.buffer
val input_view : GText.view
val last_array : string array
val mutable last_index : bool
val message_buffer : GText.buffer
val message_view : GText.view
val proof_buffer : GText.buffer
val proof_view : GText.view
val mutable is_active : bool
method is_active : bool
method activate : unit -> unit
method active_keypress_handler : GdkEvent.Key.t -> bool
method backtrack_to : GText.iter -> unit
method backtrack_to_insert : unit
method clear_message : unit
method deactivate : unit -> unit
method disconnected_keypress_handler : GdkEvent.Key.t -> bool
method electric_handler : GtkSignal.id
method find_phrase_starting_at :
GText.iter -> (GText.iter * GText.iter) option
method get_insert : GText.iter
method get_start_of_input : GText.iter
method insert_command : string -> string -> unit
method insert_commands : (string * string) list -> unit
method insert_message : string -> unit
method insert_this_phrase_on_success :
bool -> bool -> bool -> string -> string -> bool
method process_next_phrase : bool -> bool
method process_until_insert_or_error : unit
method process_until_iter_or_error : GText.iter -> unit
method process_until_end_or_error : unit
method recenter_insert : unit
method reset_initial : unit
method send_to_coq :
string ->
bool -> bool -> bool -> (Util.loc * Vernacexpr.vernac_expr) option
method set_message : string -> unit
method show_goals : unit
method undo_last_step : unit
end
let (input_views:analyzed_views viewable_script Vector.t) = Vector.create ()
let crash_save i =
Pervasives.prerr_endline "Trying to save all buffers in .crashcoqide files";
let count = ref 0 in
Vector.iter
(function {view=view;filename=filename} ->
let filename = match filename with
| None -> incr count; "Unamed_coqscript_"^(string_of_int !count)^".crashcoqide"
| Some f -> f^".crashcoqide"
in
try
try_export filename (view#buffer#get_text ());
Pervasives.prerr_endline ("Saved "^filename)
with _ -> Pervasives.prerr_endline ("Could not save "^filename)
)
input_views;
Pervasives.prerr_endline "Done. Please report.";
exit i
let _ =
let signals_to_catch = [Sys.sigabrt; Sys.sigalrm; Sys.sigfpe; Sys.sighup; Sys.sigill;
Sys.sigint; Sys.sigpipe; Sys.sigquit; Sys.sigsegv;
Sys.sigterm; Sys.sigusr2]
in List.iter (fun i -> Sys.set_signal i (Sys.Signal_handle crash_save)) signals_to_catch
let add_input_view tv =
Vector.append input_views tv
let get_input_view i = Vector.get input_views i
let active_view = ref None
let get_active_view () = Vector.get input_views (out_some !active_view)
let set_active_view i =
(match !active_view with None -> () | Some i ->
reset_tab_label i);
(notebook ())#goto_page i;
let txt = get_current_tab_label () in
set_current_tab_label (""^txt^"");
active_view := Some i
(* let kill_input_view i =
if (Array.length !input_views) <= 1 then input_views := [||]
else
let r = Array.create (Array.length !input_views) !input_views.(0) in
Array.iteri (fun j tv ->
if j < i then r.(j) <- !input_views.(j)
else if j > i then r.(j-1) <- !input_views.(j))
!input_views;
input_views := r
*)
let get_current_view () = Vector.get input_views (notebook ())#current_page
let status = ref None
let push_info = ref (function s -> failwith "not ready")
let pop_info = ref (function s -> failwith "not ready")
let flash_info = ref (function s -> failwith "not ready")
let input_channel b ic =
let buf = String.create 1024 and len = ref 0 in
while len := input ic buf 0 1024; !len > 0 do
Buffer.add_substring b buf 0 !len
done
let with_file name ~f =
let ic = open_in name in
try f ic; close_in ic with exn -> close_in ic; raise exn
type info = {start:GText.mark;
stop:GText.mark;
ast:Util.loc * Vernacexpr.vernac_expr;
reset_info:Coq.reset_info;
}
exception Size of int
let (processed_stack:info Stack.t) = Stack.create ()
let push x = Stack.push x processed_stack
let pop () = try Stack.pop processed_stack with Stack.Empty -> raise (Size 0)
let top () = try Stack.top processed_stack with Stack.Empty -> raise (Size 0)
let is_empty () = Stack.is_empty processed_stack
(* push a new Coq phrase *)
let update_on_end_of_proof () =
let lookup_lemma = function
| { ast = _, ( VernacDefinition (_, _, ProveBody _, _, _)
| VernacStartTheoremProof _) ; reset_info = Reset (_, r) } ->
r := true; raise Exit
| { ast = _, (VernacAbort _ | VernacAbortAll) } -> raise Exit
| _ -> ()
in
try Stack.iter lookup_lemma processed_stack with Exit -> ()
let update_on_end_of_segment id =
let lookup_section = function
| { ast = _, ( VernacBeginSection id'
| VernacDefineModule (id',_,_,None)
| VernacDeclareModule (id',_,_,None)
| VernacDeclareModuleType (id',_,None));
reset_info = Reset (_, r) }
when id = id' -> raise Exit
| { reset_info = Reset (_, r) } -> r := false
| _ -> ()
in
try Stack.iter lookup_section processed_stack with Exit -> ()
let push_phrase start_of_phrase_mark end_of_phrase_mark ast =
let x = {start = start_of_phrase_mark;
stop = end_of_phrase_mark;
ast = ast;
reset_info = Coq.compute_reset_info (snd ast)} in
push x;
match snd ast with
| VernacEndProof (_, None) -> update_on_end_of_proof ()
| VernacEndSegment id -> update_on_end_of_segment id
| _ -> ()
let repush_phrase x =
let x = { x with reset_info = Coq.compute_reset_info (snd x.ast) } in
push x;
match snd x.ast with
| VernacEndProof (_, None) -> update_on_end_of_proof ()
| VernacEndSegment id -> update_on_end_of_segment id
| _ -> ()
(* For electric handlers *)
exception Found
(* For find_phrase_starting_at *)
exception Stop of int
let set_break () =
Sys.set_signal Sys.sigusr1 (Sys.Signal_handle (fun _ -> raise Sys.Break))
let unset_break () =
Sys.set_signal Sys.sigusr1 Sys.Signal_ignore
(* Signal sigusr1 is used to stop coq computation *)
let pid = Unix.getpid ()
let break () = Unix.kill pid Sys.sigusr1
let can_break () = set_break ()
let cant_break () = unset_break ()
(* Get back the standard coq out channels *)
let read_stdout,clear_stdout =
let out_buff = Buffer.create 100 in
Pp_control.std_ft := Format.formatter_of_buffer out_buff;
(fun () -> Format.pp_print_flush !Pp_control.std_ft ();
let r = Buffer.contents out_buff in
Buffer.clear out_buff; r),
(fun () ->
Format.pp_print_flush !Pp_control.std_ft (); Buffer.clear out_buff)
let find_tag_limits (tag :GText.tag) (it:GText.iter) =
(if not (it#begins_tag (Some tag))
then it#backward_to_tag_toggle (Some tag)
else it#copy),
(if not (it#ends_tag (Some tag))
then it#forward_to_tag_toggle (Some tag)
else it#copy)
let activate_input i =
(match !active_view with
| None -> ()
| Some n ->
let a_v = out_some (Vector.get input_views n).analyzed_view in
prerr_endline ("DEACT"^(string_of_int n));
a_v#deactivate ();
a_v#reset_initial
);
let activate_function = (out_some (Vector.get input_views i).analyzed_view)#activate in
prerr_endline ("ACT"^(string_of_int i));
activate_function ();
prerr_endline ("ACTIVATED"^(string_of_int i));
set_active_view i
class analyzed_view index =
let {view = input_view_} as current_all_ = get_input_view index in
let proof_view_ = out_some !proof_view in
let message_view_ = out_some !message_view in
object(self)
val current_all = current_all_
val input_view = current_all_.view
val proof_view = out_some !proof_view
val message_view = out_some !message_view
val input_buffer = input_view_#buffer
val proof_buffer = proof_view_#buffer
val message_buffer = message_view_#buffer
val mutable is_active = false
method is_active = is_active
method insert_message s =
message_buffer#insert s;
message_view#misc#draw None
method set_message s =
message_buffer#set_text s;
message_view#misc#draw None
method clear_message = message_buffer#set_text ""
val mutable last_index = true
val last_array = [|"";""|]
method get_start_of_input = input_buffer#get_iter_at_mark (`NAME "start_of_input")
method get_insert = get_insert input_buffer
method recenter_insert =
ignore (input_view#scroll_to_iter ~within_margin:0.10 self#get_insert)
method show_goals =
proof_view#buffer#set_text "";
let s = Coq.get_current_goals () in
let last_shown_area = proof_buffer#create_tag [`BACKGROUND "light blue"]
in
match s with
| [] -> proof_buffer#insert (Coq.print_no_goal ())
| (hyps,concl)::r ->
let goal_nb = List.length s in
proof_buffer#insert (Printf.sprintf "%d subgoal%s\n"
goal_nb
(if goal_nb<=1 then "" else "s"));
let coq_menu commands =
let tag = proof_buffer#create_tag []
in
ignore
(tag#connect#event ~callback:
(fun ~origin ev it ->
match GdkEvent.get_type ev with
| `BUTTON_PRESS ->
let ev = (GdkEvent.Button.cast ev) in
if (GdkEvent.Button.button ev) = 3
then begin
let loc_menu = GMenu.menu () in
let factory = new GMenu.factory loc_menu in
let add_coq_command (cp,ip) =
ignore (factory#add_item cp
~callback:
(fun () -> ignore
(self#insert_this_phrase_on_success
true
true
false
(ip^"\n")
(ip^"\n"))
)
)
in
List.iter add_coq_command commands;
loc_menu#popup
~button:3
~time:(GdkEvent.Button.time ev);
end
| `MOTION_NOTIFY ->
proof_buffer#remove_tag
~start:proof_buffer#start_iter
~stop:proof_buffer#end_iter
last_shown_area;
let s,e = find_tag_limits tag
(new GText.iter it)
in
proof_buffer#apply_tag
~start:s
~stop:e
last_shown_area;
()
| _ -> ())
);
tag
in
List.iter
(fun ((_,_,_,(s,_)) as hyp) ->
let tag = coq_menu (hyp_menu hyp) in
proof_buffer#insert ~tags:[tag] (s^"\n"))
hyps;
proof_buffer#insert ("---------------------------------------(1/"^
(string_of_int goal_nb)^
")\n")
;
let tag = coq_menu (concl_menu concl) in
let _,_,_,sconcl = concl in
proof_buffer#insert ~tags:[tag] sconcl;
proof_buffer#insert "\n";
let my_mark = `NAME "end_of_conclusion" in
proof_buffer#move_mark
~where:((proof_buffer#get_iter_at_mark `INSERT)) my_mark;
proof_buffer#insert "\n\n";
let i = ref 1 in
List.iter
(function (_,(_,_,_,concl)) ->
incr i;
proof_buffer#insert ("--------------------------------------("^
(string_of_int !i)^
"/"^
(string_of_int goal_nb)^
")\n");
proof_buffer#insert concl;
proof_buffer#insert "\n\n";
)
r;
ignore (proof_view#scroll_to_mark my_mark)
method send_to_coq phrase show_output show_error localize =
try
!push_info "Coq is computing";
(out_some !status)#misc#draw None;
input_view#set_editable false;
can_break ();
let r = Some (Coq.interp phrase) in
cant_break ();
input_view#set_editable true;
!pop_info ();
(out_some !status)#misc#draw None;
let msg = read_stdout () in
self#insert_message (if show_output then msg else "");
r
with e ->
input_view#set_editable true;
!pop_info ();
(if show_error then
let (s,loc) = Coq.process_exn e in
assert (Glib.Utf8.validate s);
self#set_message s;
message_view#misc#draw None;
if localize then
(match loc with
| None -> ()
| Some (start,stop) ->
let convert_pos = byte_offset_to_char_offset phrase in
let start = convert_pos start in
let stop = convert_pos stop in
let i = self#get_start_of_input in
let starti = i#forward_chars start in
let stopi = i#forward_chars stop in
input_buffer#apply_tag_by_name "error"
~start:starti
~stop:stopi
));
None
method find_phrase_starting_at (start:GText.iter) =
let trash_bytes = ref "" in
let end_iter = start#copy in
let lexbuf_function s count =
let i = ref 0 in
let n_trash = String.length !trash_bytes in
String.blit !trash_bytes 0 s 0 n_trash;
i := n_trash;
try
while !i <= count - 1 do
let c = end_iter#char in
if c = 0 then raise (Stop !i);
let c' = Glib.Utf8.from_unichar c in
let n = String.length c' in
if n > count - !i then
begin
let ri = count - !i in
String.blit c' 0 s !i ri;
trash_bytes := String.sub c' ri (n-ri);
i := count ;
end else begin
String.blit c' 0 s !i n;
i:= !i + n
end;
if not end_iter#nocopy#forward_char then
raise (Stop !i)
done;
count
with Stop x -> x
in
try
Find_phrase.length := 0;
trash_bytes := "";
let phrase = Find_phrase.next_phrase (Lexing.from_function lexbuf_function) in
end_iter#nocopy#set_offset (start#offset + !Find_phrase.length);
Some (start,end_iter)
with _ -> None
method process_next_phrase display_goals =
self#clear_message;
match (self#find_phrase_starting_at self#get_start_of_input)
with None -> false
| Some(start,stop) ->
let b = input_buffer in
let phrase = start#get_slice ~stop in
match self#send_to_coq phrase true true true with
| Some ast ->
begin
b#move_mark ~where:stop (`NAME "start_of_input");
b#apply_tag_by_name "processed" ~start ~stop;
if (self#get_insert#compare) stop <= 0 then
begin
b#place_cursor stop;
self#recenter_insert
end;
let start_of_phrase_mark = `MARK (b#create_mark start) in
let end_of_phrase_mark = `MARK (b#create_mark stop) in
push_phrase start_of_phrase_mark end_of_phrase_mark ast;
if display_goals then
(try self#show_goals with e ->
prerr_endline (Printexc.to_string e);());
true;
end
| None -> false
method insert_this_phrase_on_success show_output show_msg localize coqphrase insertphrase =
match self#send_to_coq coqphrase show_output show_msg localize with
| Some ast ->
begin
let stop = self#get_start_of_input in
if stop#starts_line then
input_buffer#insert ~iter:stop insertphrase
else input_buffer#insert ~iter:stop ("\n"^insertphrase);
let start = self#get_start_of_input in
input_buffer#move_mark ~where:stop (`NAME "start_of_input");
input_buffer#apply_tag_by_name "processed" ~start ~stop;
if (self#get_insert#compare) stop <= 0 then
input_buffer#place_cursor stop;
let start_of_phrase_mark = `MARK (input_buffer#create_mark start) in
let end_of_phrase_mark = `MARK (input_buffer#create_mark stop) in
push_phrase start_of_phrase_mark end_of_phrase_mark ast;
(try self#show_goals with e -> ());
(try (match Coq.get_current_goals () with
| [] ->
(match self#send_to_coq "Save.\n" true true true with
| Some ast ->
begin
let stop = self#get_start_of_input in
if stop#starts_line then
input_buffer#insert ~iter:stop "Save.\n"
else input_buffer#insert ~iter:stop "\nSave.\n";
let start = self#get_start_of_input in
input_buffer#move_mark ~where:stop (`NAME "start_of_input");
input_buffer#apply_tag_by_name "processed" ~start ~stop;
if (self#get_insert#compare) stop <= 0 then
input_buffer#place_cursor stop;
let start_of_phrase_mark = `MARK (input_buffer#create_mark start) in
let end_of_phrase_mark = `MARK (input_buffer#create_mark stop) in
push_phrase start_of_phrase_mark end_of_phrase_mark ast
end
| None -> ())
| _ -> ())
with _ -> ());
true
end
| None -> self#insert_message ("Unsuccesfully tried: "^coqphrase);
false
method process_until_iter_or_error stop =
let start = self#get_start_of_input#copy in
input_buffer#apply_tag_by_name
~start
~stop
"to_process";
while ((stop#compare self#get_start_of_input>=0) && self#process_next_phrase false)
do () done;
(try self#show_goals with _ -> ());
input_buffer#remove_tag_by_name ~start ~stop "to_process" ;
method process_until_end_or_error = self#process_until_iter_or_error input_buffer#end_iter
method process_until_insert_or_error =
let stop = self#get_insert in
self#process_until_iter_or_error stop
method reset_initial =
Stack.iter
(function inf ->
let start = input_buffer#get_iter_at_mark inf.start in
let stop = input_buffer#get_iter_at_mark inf.stop in
input_buffer#move_mark ~where:start (`NAME "start_of_input");
input_buffer#remove_tag_by_name "processed" ~start ~stop;
input_buffer#delete_mark inf.start;
input_buffer#delete_mark inf.stop;
)
processed_stack;
Stack.clear processed_stack;
self#clear_message;
Coq.reset_initial ()
(* backtrack Coq to the phrase preceding iterator [i] *)
method backtrack_to i =
(* re-synchronize Coq to the current state of the stack *)
let rec synchro () =
if is_empty () then
Coq.reset_initial ()
else begin
let t = pop () in
begin match t.reset_info with
| Reset (id, ({contents=true} as v)) -> v:=false; reset_to id
| _ -> synchro ()
end;
interp_last t.ast;
repush_phrase t
end
in
let add_undo t = match t with | Some n -> Some (succ n) | None -> None
in
(* pop Coq commands until we reach iterator [i] *)
let rec pop_commands done_smthg undos =
if is_empty () then
done_smthg, undos
else
let t = top () in
if i#compare (input_buffer#get_iter_at_mark t.stop) < 0 then begin
ignore (pop ());
let undos = if is_tactic (snd t.ast) then add_undo undos else None in
pop_commands true undos
end else
done_smthg, undos
in
let done_smthg, undos = pop_commands false (Some 0) in
if done_smthg then
begin
(match undos with
| None -> synchro ()
| Some n -> try Pfedit.undo n with _ -> synchro ());
let start = if is_empty () then input_buffer#start_iter
else input_buffer#get_iter_at_mark (top ()).stop
in
input_buffer#remove_tag_by_name
~start
~stop:self#get_start_of_input
"processed";
input_buffer#move_mark ~where:start (`NAME "start_of_input");
input_buffer#place_cursor start;
(try self#show_goals with e -> ());
clear_stdout ();
self#clear_message
end
method backtrack_to_insert = self#backtrack_to self#get_insert
method undo_last_step =
try
let last_command = top () in
let start = input_buffer#get_iter_at_mark last_command.start in
let update_input () =
input_buffer#remove_tag_by_name
~start
~stop:(input_buffer#get_iter_at_mark last_command.stop)
"processed";
input_buffer#move_mark
~where:start
(`NAME "start_of_input");
input_buffer#place_cursor start;
self#recenter_insert;
(try self#show_goals with e -> ());
self#clear_message
in
begin match last_command with
| {ast=_,VernacSolve _} ->
begin
try Pfedit.undo 1; ignore (pop ()); update_input ()
with _ -> self#backtrack_to start
end
| {reset_info=Reset (id, {contents=true})} ->
ignore (pop ());
reset_to id;
update_input ()
| { ast = _, ( VernacStartTheoremProof _
| VernacDefinition (_,_,ProveBody _,_,_)) } ->
ignore (pop ());
Pfedit.delete_current_proof ();
update_input ()
| _ ->
self#backtrack_to start
end
with
| Size 0 -> !flash_info "Nothing to Undo"
method insert_command cp ip =
self#clear_message;
ignore (self#insert_this_phrase_on_success true false false cp ip)
method insert_commands l =
self#clear_message;
ignore (List.exists
(fun (cp,ip) ->
self#insert_this_phrase_on_success true false false cp ip) l)
method active_keypress_handler k =
match GdkEvent.Key.state k with
| l when List.mem `MOD1 l ->
let k = GdkEvent.Key.keyval k in
if GdkKeysyms._Return=k
then ignore(
if (input_buffer#insert_interactive "\n") then
begin
let i= self#get_insert#backward_word_start in
input_buffer#place_cursor i;
self#process_until_insert_or_error
end);
true
| l when List.mem `CONTROL l ->
let k = GdkEvent.Key.keyval k in
if GdkKeysyms._c=k
then break ();
false
| l -> false
method disconnected_keypress_handler k =
match GdkEvent.Key.state k with
| l when List.mem `CONTROL l ->
let k = GdkEvent.Key.keyval k in
if GdkKeysyms._c=k
then break ();
false
| l -> false
val mutable deact_id = None
val mutable act_id = None
method deactivate () =
is_active <- false;
(match act_id with None -> ()
| Some id ->
reset_initial ();
input_view#misc#disconnect id;
prerr_endline "DISCONNECTED old active : ";
print_id id;
);
deact_id <- Some
(input_view#event#connect#key_press self#disconnected_keypress_handler);
prerr_endline "CONNECTED inactive : ";
print_id (out_some deact_id)
method activate () =
is_active <- true;
(match deact_id with None -> ()
| Some id -> input_view#misc#disconnect id;
prerr_endline "DISCONNECTED old inactive : ";
print_id id
);
act_id <- Some
(input_view#event#connect#key_press self#active_keypress_handler);
prerr_endline "CONNECTED active : ";
print_id (out_some act_id);
Sys.chdir (match (Vector.get input_views index).filename with
| None -> initial_cwd
| Some f -> Filename.dirname f
)
method electric_handler =
input_buffer#connect#insert_text ~callback:
(fun it x ->
begin try
if last_index then begin
last_array.(0)<-x;
if (last_array.(1) ^ last_array.(0) = ".\n") then raise Found
end else begin
last_array.(1)<-x;
if (last_array.(0) ^ last_array.(1) = ".\n") then raise Found
end
with Found ->
begin
ignore (self#process_next_phrase true)
end;
end;
last_index <- not last_index;)
initializer
ignore (message_buffer#connect#after#insert_text
~callback:(fun it s -> ignore
(message_view#scroll_to_mark
~within_margin:0.49
`INSERT)));
ignore (input_buffer#connect#modified_changed
~callback:(fun () ->
if input_buffer#modified then
set_tab_image index
(match current_all.filename with
| None -> saveas_icon
| Some _ -> save_icon
)
else set_tab_image index yes_icon;
));
ignore (input_buffer#connect#changed
~callback:(fun () ->
input_buffer#remove_tag_by_name
~start:self#get_start_of_input
~stop:input_buffer#end_iter
"error";
Highlight.highlight_current_line input_buffer));
end
let create_input_tab filename =
let b = GText.buffer () in
let tablabel = GMisc.label () in
let v_box = GPack.hbox ~homogeneous:false () in
let image = GMisc.image ~packing:v_box#pack () in
let label = GMisc.label ~text:filename ~packing:v_box#pack () in
let fr1 = GBin.frame ~shadow_type:`ETCHED_OUT
~packing:((notebook ())#append_page ~tab_label:v_box#coerce) ()
in
let sw1 = GBin.scrolled_window
~vpolicy:`AUTOMATIC
~hpolicy:`AUTOMATIC
~packing:fr1#add ()
in
let tv1 = GText.view ~buffer:b ~packing:(sw1#add) () in
tv1#misc#set_name "ScriptWindow";
let _ = tv1#set_editable true in
let _ = tv1#set_wrap_mode `CHAR in
b#place_cursor ~where:(b#start_iter);
ignore (tv1#event#connect#button_press ~callback:
(fun ev -> GdkEvent.Button.button ev = 3));
tv1#misc#grab_focus ();
ignore (tv1#buffer#create_mark
~name:"start_of_input"
tv1#buffer#start_iter);
ignore (tv1#buffer#create_tag
~name:"to_process"
[`BACKGROUND "light blue" ;`EDITABLE false]);
ignore (tv1#buffer#create_tag
~name:"processed"
[`BACKGROUND "light green" ;`EDITABLE false]);
ignore (tv1#buffer#create_tag
~name:"error"
[`UNDERLINE `DOUBLE ; `FOREGROUND "red"]);
ignore (tv1#buffer#create_tag
~name:"kwd"
[`FOREGROUND "blue"]);
ignore (tv1#buffer#create_tag
~name:"decl"
[`FOREGROUND "orange red"]);
ignore (tv1#buffer#create_tag
~name:"comment"
[`FOREGROUND "brown"]);
ignore (tv1#buffer#create_tag
~name:"reserved"
[`FOREGROUND "dark red"]);
tv1
let main () =
let w = GWindow.window
~allow_grow:true ~allow_shrink:true
~width:window_width ~height:window_height
~title:"CoqIde" ()
in
let accel_group = GtkData.AccelGroup.create () in
let vbox = GPack.vbox ~homogeneous:false ~packing:w#add () in
let menubar = GMenu.menu_bar ~packing:vbox#pack () in
let factory = new GMenu.factory menubar in
let accel_group = factory#accel_group in
(* File Menu *)
let file_menu = factory#add_submenu "File" in
let file_factory = new GMenu.factory file_menu ~accel_group in
(* File/Load Menu *)
let load_m = file_factory#add_item "Open" ~key:GdkKeysyms._O in
let load_f () =
match GToolbox.select_file ~title:"Load file" () with
| None -> ()
| Some f ->
try
let b = Buffer.create 1024 in
with_file f ~f:(input_channel b);
let s = try_convert (Buffer.contents b) in
let view = create_input_tab (Filename.basename f) in
(match !manual_monospace_font with
| None -> ()
| Some n -> view#misc#modify_font n);
let index = add_input_view {view = view;
analyzed_view = None;
filename = Some f
}
in
(get_input_view index).analyzed_view <- Some (new analyzed_view index);
activate_input index;
let input_buffer = view#buffer in
input_buffer#set_text s;
input_buffer#place_cursor input_buffer#start_iter;
Highlight.highlight_all input_buffer;
input_buffer#set_modified false
with e -> !flash_info "Load failed"
in
ignore (load_m#connect#activate load_f);
(* File/Save Menu *)
let save_m = file_factory#add_item "Save" ~key:GdkKeysyms._S in
let save_f () =
let current = get_current_view () in
try (match current.filename with
| None ->
begin match GToolbox.select_file ~title:"Save file" ()
with
| None -> ()
| Some f ->
try_export f (current.view#buffer#get_text ());
current.filename <- Some f;
set_current_tab_label (Filename.basename f);
current.view#buffer#set_modified false
end
| Some f ->
try_export f (current.view#buffer#get_text ());
current.view#buffer#set_modified false);
!flash_info "Saved"
with e -> !flash_info "Save failed"
in
ignore (save_m#connect#activate save_f);
(* File/Save As Menu *)
let saveas_m = file_factory#add_item "Save as" in
let saveas_f () =
let current = get_current_view () in
try (match current.filename with
| None ->
begin match GToolbox.select_file ~title:"Save file as" ()
with
| None -> ()
| Some f ->
try_export f (current.view#buffer#get_text ());
current.filename <- Some f;
set_current_tab_label (Filename.basename f);
current.view#buffer#set_modified false
end
| Some f ->
begin match GToolbox.select_file
~dir:(ref (Filename.dirname f))
~filename:(Filename.basename f)
~title:"Save file as" ()
with
| None -> ()
| Some f ->
try_export f (current.view#buffer#get_text ());
current.filename <- Some f;
set_current_tab_label (Filename.basename f);
current.view#buffer#set_modified false
end);
!flash_info "Saved"
with e -> !flash_info "Save failed"
in
ignore (saveas_m#connect#activate saveas_f);
(* File/Save All Menu *)
let saveall_m = file_factory#add_item "Save All" in
let saveall_f () =
Vector.iter
(fun {view = view ; filename = filename} ->
match filename with
| None -> ()
| Some f ->
try_export f (view#buffer#get_text ());
view#buffer#set_modified false
) input_views
in
let has_something_to_save () =
Vector.exists
(fun {view=view} -> view#buffer#modified)
input_views
in
ignore (saveall_m#connect#activate saveall_f);
(* File/Revert Menu *)
let revert_m = file_factory#add_item "Revert" in
revert_m#misc#set_state `INSENSITIVE;
(* File/Print Menu *)
let print_m = file_factory#add_item "Print" in
print_m#misc#set_state `INSENSITIVE;
(* File/Export to Menu *)
let file_export_m = file_factory#add_submenu "Export to" in
let file_export_factory = new GMenu.factory file_export_m ~accel_group in
let export_html_m = file_export_factory#add_item "Html" in
export_html_m#misc#set_state `INSENSITIVE;
let export_latex_m = file_export_factory#add_item "LaTeX" in
export_latex_m#misc#set_state `INSENSITIVE;
let export_dvi_m = file_export_factory#add_item "Dvi" in
export_dvi_m#misc#set_state `INSENSITIVE;
let export_ps_m = file_export_factory#add_item "Ps" in
export_ps_m#misc#set_state `INSENSITIVE;
(* File/Rehighlight Menu *)
let rehighlight_m = file_factory#add_item "Rehighlight" ~key:GdkKeysyms._L in
ignore (rehighlight_m#connect#activate
(fun () -> Highlight.highlight_all
(get_current_view()).view#buffer));
(* File/Refresh Menu *)
let refresh_m = file_factory#add_item "Restart all" ~key:GdkKeysyms._R in
refresh_m#misc#set_state `INSENSITIVE;
(* Fiel/Quit Menu *)
let quit_f () =
if has_something_to_save () then
match (GToolbox.question_box ~title:"Quit"
~buttons:["Save Named Buffers and Quit";
"Don't Save and Quit";
"Don't Quit"]
~default:0
~icon:
(let img = GMisc.image () in
img#set_stock "gtk-dialog-warning" ~size:6;
img#coerce)
"There are unsaved buffers"
)
with 1 -> saveall_f () ; exit 0
| 2 -> exit 0
| _ -> ()
else exit 0
in
let quit_m = file_factory#add_item "Quit" ~key:GdkKeysyms._Q ~callback:quit_f
in
ignore (w#event#connect#delete (fun _ -> quit_f (); true));
(* Navigation Menu *)
let navigation_menu = factory#add_submenu "Navigation" in
let navigation_factory = new GMenu.factory navigation_menu ~accel_group ~accel_modi:[`CONTROL ; `MOD1] in
let do_or_activate f () =
let current = get_current_view () in
let analyzed_view = out_some current.analyzed_view in
if analyzed_view#is_active then
ignore (f analyzed_view)
else
activate_input (notebook ())#current_page
in
ignore (navigation_factory#add_item "Forward"
~key:GdkKeysyms._Down
~callback:(do_or_activate (fun a -> a#process_next_phrase true)));
ignore (navigation_factory#add_item "Backward"
~key:GdkKeysyms._Up
~callback:(do_or_activate (fun a -> a#undo_last_step)));
ignore (navigation_factory#add_item "Forward to"
~key:GdkKeysyms._Right
~callback:(do_or_activate (fun a -> a#process_until_insert_or_error))
);
ignore (navigation_factory#add_item "Backward to"
~key:GdkKeysyms._Left
~callback:(do_or_activate (fun a-> a#backtrack_to_insert))
);
ignore (navigation_factory#add_item "Start"
~key:GdkKeysyms._Home
~callback:(do_or_activate (fun a -> a#reset_initial))
);
ignore (navigation_factory#add_item "End"
~key:GdkKeysyms._End
~callback:(do_or_activate (fun a -> a#process_until_end_or_error))
);
(* Tactics Menu *)
let tactics_menu = factory#add_submenu "Tactics" in
let tactics_factory = new GMenu.factory tactics_menu ~accel_group ~accel_modi:[`MOD1] in
let do_if_active f () =
let current = get_current_view () in
let analyzed_view = out_some current.analyzed_view in
if analyzed_view#is_active then ignore (f analyzed_view)
in
ignore (tactics_factory#add_item "Auto"
~key:GdkKeysyms._a
~callback:(do_if_active (fun a -> a#insert_command "Progress Auto.\n" "Auto.\n"))
);
ignore (tactics_factory#add_item "Auto with *"
~key:GdkKeysyms._asterisk
~callback:(do_if_active (fun a -> a#insert_command
"Progress Auto with *.\n"
"Auto with *.\n")));
ignore (tactics_factory#add_item "EAuto"
~key:GdkKeysyms._e
~callback:(do_if_active (fun a -> a#insert_command
"Progress EAuto.\n"
"EAuto.\n"))
);
ignore (tactics_factory#add_item "EAuto with *"
~key:GdkKeysyms._ampersand
~callback:(do_if_active (fun a -> a#insert_command
"Progress EAuto with *.\n"
"EAuto with *.\n"))
);
ignore (tactics_factory#add_item "Intuition"
~key:GdkKeysyms._i
~callback:(do_if_active (fun a -> a#insert_command "Progress Intuition.\n" "Intuition.\n"))
);
ignore (tactics_factory#add_item "Omega"
~key:GdkKeysyms._o
~callback:(do_if_active (fun a -> a#insert_command "Omega.\n" "Omega.\n"))
);
ignore (tactics_factory#add_item "Simpl"
~key:GdkKeysyms._s
~callback:(do_if_active (fun a -> a#insert_command "Progress Simpl.\n" "Simpl.\n" ))
);
ignore (tactics_factory#add_item "Tauto"
~key:GdkKeysyms._t
~callback:(do_if_active (fun a -> a#insert_command "Tauto.\n" "Tauto.\n" ))
);
ignore (tactics_factory#add_item "Trivial"
~key:GdkKeysyms._v
~callback:(do_if_active( fun a -> a#insert_command "Progress Trivial.\n" "Trivial.\n" ))
);
ignore (tactics_factory#add_item ""
~key:GdkKeysyms._dollar
~callback:(do_if_active (fun a -> a#insert_commands
["Progress Trivial.\n","Trivial.\n";
"Progress Auto.\n","Auto.\n";
"Tauto.\n","Tauto.\n";
"Omega.\n","Omega.\n";
"Progress Auto with *.\n","Auto with *.\n";
"Progress EAuto with *.\n","EAuto with *.\n";
"Progress Intuition.\n","Intuition.\n";
]))
);
(* Templates Menu *)
let templates_menu = factory#add_submenu "Templates" in
let templates_factory = new GMenu.factory templates_menu ~accel_group ~accel_modi:[`MOD1] in
let templates_tactics = templates_factory#add_submenu "Tactics" in
let templates_tactics_factory = new GMenu.factory templates_tactics ~accel_group in
ignore (templates_tactics_factory#add_item "Auto");
ignore (templates_tactics_factory#add_item "Auto with *");
ignore (templates_tactics_factory#add_item "EAuto");
ignore (templates_tactics_factory#add_item "EAuto with *");
ignore (templates_tactics_factory#add_item "Intuition");
ignore (templates_tactics_factory#add_item "Omega");
ignore (templates_tactics_factory#add_item "Simpl");
ignore (templates_tactics_factory#add_item "Tauto");
ignore (templates_tactics_factory#add_item "Trivial");
let templates_commands = templates_factory#add_submenu "Commands" in
let templates_commands_factory = new GMenu.factory templates_commands
~accel_group
~accel_modi:[`MOD1]
in
(* Templates/Commands/Lemma *)
let callback () =
let {view = view } = get_current_view () in
if (view#buffer#insert_interactive "Lemma new_lemma : .\nProof.\n\nSave.\n") then
begin
let iter = view#buffer#get_iter_at_mark `INSERT in
ignore (iter#nocopy#backward_chars 19);
view#buffer#move_mark `INSERT iter;
ignore (iter#nocopy#backward_chars 9);
view#buffer#move_mark `SEL_BOUND iter;
Highlight.highlight_all view#buffer
end
in
ignore (templates_commands_factory#add_item "Lemma _" ~callback ~key:GdkKeysyms._L);
(* Commands Menu *)
let commands_menu = factory#add_submenu "Commands" in
let commands_factory = new GMenu.factory commands_menu ~accel_group in
ignore (commands_factory#add_item "Compile");
ignore (commands_factory#add_item "Make");
ignore (commands_factory#add_item "Make Makefile");
(* Configuration Menu *)
let configuration_menu = factory#add_submenu "Configuration" in
let configuration_factory = new GMenu.factory configuration_menu ~accel_group
in
let customize_colors_m =
configuration_factory#add_item "Customize colors"
~callback:(fun () -> !flash_info "Not implemented")
in
font_selector :=
Some (GWindow.font_selection_dialog
~title:"Select font..."
~modal:true ());
let font_selector = out_some !font_selector in
font_selector#selection#set_font_name default_monospace_font_name;
font_selector#selection#set_preview_text
"Lemma Truth: (p:Prover) `p < Coq`. Proof. Auto with *. Save.";
let customize_fonts_m =
configuration_factory#add_item "Customize fonts"
~callback:(fun () -> font_selector#present ())
in
let hb = GPack.paned `HORIZONTAL ~border_width:3 ~packing:vbox#add () in
let _ = hb#set_position (window_width/2 ) in
_notebook := Some (GPack.notebook ~packing:hb#add1 ());
let nb = notebook () in
let fr2 = GBin.frame ~shadow_type:`ETCHED_OUT ~packing:hb#add2 () in
let hb2 = GPack.paned `VERTICAL ~border_width:3 ~packing:fr2#add () in
hb2#set_position (window_height*7/10);
let sw2 = GBin.scrolled_window
~vpolicy:`AUTOMATIC
~hpolicy:`AUTOMATIC
~packing:(hb2#add) () in
let sw3 = GBin.scrolled_window
~vpolicy:`AUTOMATIC
~hpolicy:`AUTOMATIC
~packing:(hb2#add) () in
let status_bar = GMisc.statusbar ~packing:vbox#pack () in
let status_context = status_bar#new_context "Messages" in
ignore (status_context#push "Ready");
status := Some status_bar;
push_info := (fun s -> ignore (status_context#push s));
pop_info := (fun () -> status_context#pop ());
flash_info := (fun s -> status_context#flash ~delay:5000 s);
let tv2 = GText.view ~packing:(sw2#add) () in
tv2#misc#set_name "GoalWindow";
let _ = tv2#set_editable false in
let tb2 = tv2#buffer in
let tv3 = GText.view ~packing:(sw3#add) () in
tv2#misc#set_name "MessageWindow";
let _ = tv2#set_wrap_mode `CHAR in
let _ = tv3#set_wrap_mode `WORD in
let _ = tv3#set_editable false in
let _ = GtkBase.Widget.add_events tv2#as_widget [`POINTER_MOTION] in
let _ = tv2#event#connect#motion_notify
~callback:(fun e ->
let win = match tv2#get_window `WIDGET with
| None -> assert false
| Some w -> w
in
let x,y = Gdk.Window.get_pointer_location win in
let b_x,b_y = tv2#window_to_buffer_coords
~tag:`WIDGET
~x
~y
in
let it = tv2#get_iter_at_location ~x:b_x ~y:b_y in
let tags = it#tags in
List.iter
( fun t ->
ignore (GtkText.Tag.event
t#as_tag
tv2#as_widget
e
it#as_textiter))
tags;
false)
in
ignore (font_selector#cancel_button#connect#released
~callback:font_selector#misc#hide);
ignore (font_selector#ok_button#connect#released
~callback:(fun () ->
(match font_selector#selection#font_name with
| None -> ()
| Some n ->
let pango_font = Pango.Font.from_string n in
tv2#misc#modify_font pango_font;
tv3#misc#modify_font pango_font;
Vector.iter
(fun {view=view} -> view#misc#modify_font pango_font)
input_views;
manual_monospace_font := Some pango_font
);
font_selector#misc#hide ()));
(try
let startup_image = GdkPixbuf.from_file "coq.gif" in
tv2#buffer#insert_pixbuf ~iter:tv2#buffer#start_iter
~pixbuf:startup_image;
tv2#buffer#insert ~iter:tv2#buffer#start_iter "\t\t";
with _ -> ());
tv2#buffer#insert "\nCoqIde: an experimental Gtk2 interface for Coq.\n";
tv2#buffer#insert (try_convert (Coq.version ()));
w#add_accel_group accel_group;
(* Remove default pango menu for textviews *)
ignore (tv2#event#connect#button_press ~callback:
(fun ev -> GdkEvent.Button.button ev = 3));
ignore (tv3#event#connect#button_press ~callback:
(fun ev -> GdkEvent.Button.button ev = 3));
tv2#misc#set_can_focus false;
tv3#misc#set_can_focus false;
ignore (tv2#buffer#create_mark
~name:"end_of_conclusion"
tv2#buffer#start_iter);
ignore (tv3#buffer#create_tag
~name:"error"
[`FOREGROUND "red"]);
w#show ();
message_view := Some tv3;
proof_view := Some tv2;
let view = create_input_tab "New File" in
let index = add_input_view {view = view;
analyzed_view = None;
filename = None}
in
(get_input_view index).analyzed_view <- Some (new analyzed_view index);
activate_input index;
set_tab_image index yes_icon;
(match !manual_monospace_font with
| None -> ()
| Some f -> view#misc#modify_font f; tv2#misc#modify_font f; tv3#misc#modify_font f)
let start () =
cant_break ();
Coq.init ();
GtkMain.Rc.add_default_file ".coqiderc";
(try
GtkMain.Rc.add_default_file (Filename.concat (Sys.getenv "HOME") ".coqiderc");
with Not_found -> ());
ignore (GtkMain.Main.init ());
Glib.Message.set_log_handler ~domain:"Gtk" ~levels:[`ERROR;`FLAG_FATAL;
`WARNING;`CRITICAL]
(fun ~level msg ->
failwith ("Coqide internal error: " ^ msg)
);
main ();
Sys.catch_break true;
while true do
try
GMain.Main.main ()
with
| Sys.Break -> prerr_endline "Interrupted." ; flush stderr
| e ->
prerr_endline ("CoqIde fatal error:" ^ (Printexc.to_string e));
crash_save 127
done