aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--Makefile.build10
-rw-r--r--_tags4
-rw-r--r--ide/coq.ml490
-rw-r--r--ide/coq.mli122
-rw-r--r--ide/coqide.ml860
-rw-r--r--ide/coqide.mli6
-rw-r--r--ide/coqide_main.ml423
-rw-r--r--ide/ideutils.ml201
-rw-r--r--ide/ideutils.mli22
-rw-r--r--ide/wg_Command.ml38
-rw-r--r--ide/wg_ScriptView.ml166
-rw-r--r--ide/wg_ScriptView.mli4
-rw-r--r--myocamlbuild.ml4
13 files changed, 988 insertions, 962 deletions
diff --git a/Makefile.build b/Makefile.build
index c3768528e..24f024df4 100644
--- a/Makefile.build
+++ b/Makefile.build
@@ -302,7 +302,7 @@ plugins/micromega/csdpcert$(EXE): $(CSDPCERTCMO:.cmo=$(BESTOBJ))
# target to build CoqIde
coqide:: coqide-files coqide-binaries theories/Init/Prelude.vo
-COQIDEFLAGS=-thread $(addprefix -I , $(IDESRCDIRS)) $(COQIDEINCLUDES)
+COQIDEFLAGS=$(addprefix -I , $(IDESRCDIRS)) $(COQIDEINCLUDES)
.SUFFIXES:.vo
@@ -317,8 +317,8 @@ coqide-files: $(IDEFILES)
ifeq ($(HASCOQIDE),opt)
$(COQIDE): $(LINKIDEOPT) | $(COQTOPEXE)
$(SHOW)'OCAMLOPT -o $@'
- $(HIDE)$(OCAMLOPT) $(COQIDEFLAGS) $(OPTFLAGS) -o $@ unix.cmxa threads.cmxa \
- lablgtk.cmxa lablgtksourceview2.cmxa $(IDEOPTFLAGS) gtkThread.cmx \
+ $(HIDE)$(OCAMLOPT) $(COQIDEFLAGS) $(OPTFLAGS) -o $@ unix.cmxa \
+ lablgtk.cmxa lablgtksourceview2.cmxa $(IDEOPTFLAGS) \
str.cmxa $(LINKIDEOPT)
$(STRIP) $@
else
@@ -328,8 +328,8 @@ endif
$(COQIDEBYTE): $(LINKIDE) | $(COQTOPBYTE)
$(SHOW)'OCAMLC -o $@'
- $(HIDE)$(OCAMLC) $(COQIDEFLAGS) $(BYTEFLAGS) -o $@ unix.cma threads.cma lablgtk.cma\
- lablgtksourceview2.cma gtkThread.cmo str.cma $(LINKIDE)
+ $(HIDE)$(OCAMLC) $(COQIDEFLAGS) $(BYTEFLAGS) -o $@ unix.cma lablgtk.cma \
+ lablgtksourceview2.cma str.cma $(LINKIDE)
# install targets
diff --git a/_tags b/_tags
index b50dbfed2..f998dd76f 100644
--- a/_tags
+++ b/_tags
@@ -8,7 +8,7 @@
<tools/coq_tex.{native,byte}> : use_str
<tools/coq_makefile.{native,byte}> : use_str, use_unix
<tools/coqdoc/main.{native,byte}> : use_str
-<ide/coqide_main.{native,byte}> : use_str, use_unix, thread, ide
+<ide/coqide_main.{native,byte}> : use_str, use_unix, ide
<checker/main.{native,byte}> : use_str, use_unix
<plugins/micromega/csdpcert.{native,byte}> : use_nums, use_unix
<tools/mkwinapp.{native,byte}> : use_unix
@@ -16,7 +16,7 @@
## tags for ide
-<ide/**/*.{ml,mli}>: thread, ide
+<ide/**/*.{ml,mli}>: ide
## tags for grammar.cm*
diff --git a/ide/coq.ml b/ide/coq.ml
index 7f927042e..01c6c36b7 100644
--- a/ide/coq.ml
+++ b/ide/coq.ml
@@ -158,65 +158,127 @@ let check_connection args =
(** Useful stuff *)
-let atomically mtx f arg =
- Mutex.lock mtx;
- try
- let ans = f arg in
- Mutex.unlock mtx;
- ans
- with err ->
- Mutex.unlock mtx;
- raise err
-
let ignore_error f arg =
try ignore (f arg) with _ -> ()
+(** An abstract copy of unit.
+ This will help ensuring that we do not forget to finally call
+ continuations when building tasks in other modules. *)
+
+type void = Void
+
+(** ccb : existential type for a (call + callback) type.
+
+ Reference: http://alan.petitepomme.net/cwn/2004.01.13.html
+ To rewrite someday with GADT. *)
+
+type 'a poly_ccb = 'a Serialize.call * ('a Interface.value -> void)
+type 't scoped_ccb = { bind_ccb : 'a. 'a poly_ccb -> 't }
+type ccb = { open_ccb : 't. 't scoped_ccb -> 't }
+
+let mk_ccb poly = { open_ccb = fun scope -> scope.bind_ccb poly }
+let with_ccb ccb e = ccb.open_ccb e
+
+let interrupter = ref (fun pid -> Unix.kill pid Sys.sigint)
+let soft_killer = ref (fun pid -> Unix.kill pid Sys.sigterm)
+let killer = ref (fun pid -> Unix.kill pid Sys.sigkill)
+
+(** Handling old processes *)
+
+type unix_process_id = int
+type retry = int
+
+let zombies = ref ([] : (unix_process_id * retry) list)
+
+let pr_zombies ?(full=false) l =
+ let pr (pid,retries) =
+ string_of_int pid ^
+ if full then "["^string_of_int retries^"]" else ""
+ in
+ String.concat ", " (List.map pr l)
+
+let is_buried pid =
+ try fst (Unix.waitpid [Unix.WNOHANG] pid) <> 0
+ with Unix.Unix_error _ -> false
+
+let max_retry = 3 (* one soft then two hard kill attempts *)
+
+let countdown = ref None
+let final_countdown () = (countdown := Some max_retry)
+
+let check_zombies () =
+ (* First, do [waitpid] on our zombies and try to kill the survivors *)
+ let handle_zombie others (pid,retries) =
+ if is_buried pid then others
+ else
+ let () = match retries with
+ | 0 -> () (* freshly closed coqtop, leave it some time *)
+ | 1 -> ignore_error !soft_killer pid
+ | _ -> ignore_error !killer pid
+ in (pid,retries+1) :: others
+ in
+ let zs = List.fold_left handle_zombie [] !zombies
+ in
+ if zs <> [] then
+ Minilib.log ("Remaining zombies: "^ pr_zombies ~full:true zs);
+ (* Second, we warn the user about old zombies that refuses to die
+ (except in the middle of the final countdown) *)
+ let chk_old = !countdown = None || !countdown = Some 0 in
+ let old_zombies, new_zombies =
+ List.partition (fun (_,n) -> chk_old && n >= max_retry) zs
+ in
+ if old_zombies <> [] then begin
+ let msg1 = "Some coqtop processes are still running.\n" in
+ let msg2 = "You may have to kill them manually.\nPids: " in
+ let msg3 = pr_zombies old_zombies in
+ let popup = GWindow.message_dialog ~buttons:GWindow.Buttons.close
+ ~message_type:`ERROR ~message:(msg1 ^ msg2 ^ msg3) ()
+ in
+ ignore (popup#run ());
+ ignore (popup#destroy ())
+ end;
+ zombies := new_zombies;
+ (* Finally, we might end coqide if the final countdown has started *)
+ if !countdown = Some 0 || (!countdown <> None && zs = []) then exit 0;
+ countdown := Option.map pred !countdown;
+ true
+
+let _ = Glib.Timeout.add ~ms:300 ~callback:check_zombies
+
(** * The structure describing a coqtop sub-process *)
type handle = {
- pid : int;
- (* Unix process id *)
- cout : in_channel;
+ pid : unix_process_id;
+ cout : Unix.file_descr;
cin : out_channel;
mutable alive : bool;
- xml_parser : Xml_parser.t;
+ mutable waiting_for : (ccb * logger) option; (* last call + callback + log *)
}
-type coqtop = {
- (* lock managing coqtop access *)
- lock : Mutex.t;
- (* non quoted command-line arguments of coqtop *)
- sup_args : string list;
- (* actual coqtop process *)
- mutable handle : handle;
- (* trigger called whenever coqtop dies abruptly *)
- trigger : handle -> unit;
- (* whether coqtop may be relaunched *)
- mutable is_closed : bool;
- (* whether coqtop is computing *)
- mutable is_computing : bool;
- (* whether coqtop is waiting to be resetted *)
- mutable is_to_reset : bool;
-}
-
-(** Invariants:
- - any outside request takes the coqtop.lock and is discarded when
- [is_closed = true].
- - coqtop.handle may be written ONLY when toplvl_ctr_mtx AND coqtop.lock is
- taken.
+(** Coqtop process status :
+ - New : a process has been spawned, but not initialized via [init_coqtop].
+ It will reject tasks given via [try_grab].
+ - Ready : no current task, accepts new tasks via [try_grab].
+ - Busy : has accepted a task via [init_coqtop] or [try_grab],
+ It will reject other tasks for the moment
+ - Closed : the coqide buffer has been closed, we discard any further task.
*)
-type logger = Interface.message_level -> string -> unit
+type status = New | Ready | Busy | Closed
-exception DeadCoqtop
+type task = handle -> (unit -> void) -> void
-(** * Count of all active coqtops *)
+type reset_kind = Planned | Unexpected
-let toplvl_ctr = ref 0
-
-let toplvl_ctr_mtx = Mutex.create ()
-
-let coqtop_zombies () = !toplvl_ctr
+type coqtop = {
+ (* non quoted command-line arguments of coqtop *)
+ sup_args : string list;
+ (* called whenever coqtop dies *)
+ mutable reset_handler : reset_kind -> task;
+ (* actual coqtop process and its status *)
+ mutable handle : handle;
+ mutable status : status;
+}
(** * Starting / signaling / ending a real coqtop sub-process *)
@@ -259,201 +321,174 @@ let open_process_pid prog args =
assert (pid <> 0);
Unix.close ide2top_r;
Unix.close top2ide_w;
- let oc = Unix.out_channel_of_descr ide2top_w in
- let ic = Unix.in_channel_of_descr top2ide_r in
- set_binary_mode_out oc true;
- set_binary_mode_in ic true;
- (pid,ic,oc)
+ (pid,top2ide_r,Unix.out_channel_of_descr ide2top_w)
+
+exception TubeError
+exception AnswerWithoutRequest
+
+let install_input_watch handle respawner =
+ let io_chan = Glib.Io.channel_of_descr handle.cout in
+ let all_conds = [`ERR; `HUP; `IN; `NVAL; `PRI] in (* all except `OUT *)
+ let rec check_errors = function
+ | [] -> ()
+ | (`IN | `PRI) :: conds -> check_errors conds
+ | e :: _ -> raise TubeError
+ in
+ let handle_intermediate_message logger xml =
+ let message = Serialize.to_message xml in
+ let level = message.Interface.message_level in
+ let content = message.Interface.message_content in
+ logger level content
+ in
+ let handle_final_answer ccb xml =
+ Minilib.log "Handling coqtop answer";
+ handle.waiting_for <- None;
+ with_ccb ccb { bind_ccb = fun (c,f) -> f (Serialize.to_answer xml c) }
+ in
+ let unsafe_handle_input conds =
+ check_errors conds;
+ let s = io_read_all io_chan in
+ if s = "" then raise TubeError;
+ match handle.waiting_for with
+ |None -> raise AnswerWithoutRequest
+ |Some (ccb,logger) ->
+ let p = Xml_parser.make (Xml_parser.SString s) in
+ let rec loop () =
+ let xml = Xml_parser.parse p in
+ if Serialize.is_message xml then
+ (handle_intermediate_message logger xml; loop ())
+ else
+ ignore (handle_final_answer ccb xml)
+ in
+ try loop ()
+ with Xml_parser.Error (Xml_parser.Empty, _) -> () (* end of s *)
+ in
+ let print_exception = function
+ | Xml_parser.Error e -> Xml_parser.error e
+ | Serialize.Marshal_error -> "Protocol violation"
+ | e -> Printexc.to_string e
+ in
+ let handle_input conds =
+ if not handle.alive then false (* coqtop already terminated *)
+ else
+ try unsafe_handle_input conds; true
+ with e ->
+ Minilib.log ("Coqtop reader failed, resetting: "^print_exception e);
+ respawner ();
+ false
+ in
+ ignore (Glib.Io.add_watch ~cond:all_conds ~callback:handle_input io_chan)
(** This launches a fresh handle from its command line arguments. *)
-let unsafe_spawn_handle args =
+let spawn_handle args =
let prog = coqtop_path () in
let args = Array.of_list (prog :: "-ideslave" :: args) in
- let (pid, ic, oc) = open_process_pid prog args in
- let p = Xml_parser.make (Xml_parser.SChannel ic) in
- Xml_parser.check_eof p false;
- incr toplvl_ctr;
+ let (pid, in_fd, oc) = open_process_pid prog args in
{
pid = pid;
cin = oc;
- cout = ic;
+ cout = in_fd;
alive = true;
- xml_parser = p;
+ waiting_for = None;
}
(** This clears any potentially remaining open garbage. *)
-let unsafe_clear_handle coqtop =
- let handle = coqtop.handle in
- if handle.alive then begin
+let clear_handle h =
+ if h.alive then begin
(* invalidate the old handle *)
- handle.alive <- false;
- ignore_error close_out handle.cin;
- ignore_error close_in handle.cout;
- ignore_error (Unix.waitpid []) handle.pid;
- decr toplvl_ctr
+ h.alive <- false;
+ ignore_error close_out h.cin;
+ ignore_error Unix.close h.cout;
+ (* we monitor the death of the old process *)
+ zombies := (h.pid,0) :: !zombies
end
-let spawn_coqtop hook sup_args =
- let handle =
- atomically toplvl_ctr_mtx unsafe_spawn_handle sup_args
- in
+let mkready coqtop =
+ fun () -> coqtop.status <- Ready; Void
+
+let rec respawn_coqtop ?(why=Unexpected) coqtop =
+ clear_handle coqtop.handle;
+ ignore_error (fun () -> coqtop.handle <- spawn_handle coqtop.sup_args) ();
+ (* Normally, the handle is now a fresh one.
+ If not, there isn't much we can do ... *)
+ assert (coqtop.handle.alive = true);
+ coqtop.status <- New;
+ install_input_watch coqtop.handle (fun () -> respawn_coqtop coqtop);
+ ignore (coqtop.reset_handler why coqtop.handle (mkready coqtop))
+
+let spawn_coqtop sup_args =
+ let ct =
{
- handle = handle;
- lock = Mutex.create ();
+ handle = spawn_handle sup_args;
sup_args = sup_args;
- trigger = hook;
- is_closed = false;
- is_computing = false;
- is_to_reset = false;
+ reset_handler = (fun _ _ k -> k ());
+ status = New;
}
+ in
+ install_input_watch ct.handle (fun () -> respawn_coqtop ct);
+ ct
-let interrupter = ref (fun pid -> Unix.kill pid Sys.sigint)
-let killer = ref (fun pid -> Unix.kill pid Sys.sigkill)
+let set_reset_handler coqtop hook = coqtop.reset_handler <- hook
+
+let is_computing coqtop = (coqtop.status = Busy)
-let is_computing coqtop = coqtop.is_computing
+(* For closing a coqtop, we don't try to send it a Quit call anymore,
+ but rather close its channels:
+ - a listening coqtop will handle this just as a Quit call
+ - a busy coqtop will anyway have to be killed *)
-let is_closed coqtop = coqtop.is_closed
+let close_coqtop coqtop =
+ coqtop.status <- Closed;
+ clear_handle coqtop.handle
+
+let reset_coqtop coqtop = respawn_coqtop ~why:Planned coqtop
-(** These are asynchronous signals *)
let break_coqtop coqtop =
try !interrupter coqtop.handle.pid
with _ -> Minilib.log "Error while sending Ctrl-C"
-let kill_coqtop coqtop =
- try !killer coqtop.handle.pid
- with _ -> Minilib.log "Kill -9 failed. Process already terminated ?"
+let process_task coqtop task =
+ assert (coqtop.status = Ready || coqtop.status = New);
+ coqtop.status <- Busy;
+ try ignore (task coqtop.handle (mkready coqtop))
+ with e ->
+ Minilib.log ("Coqtop writer failed, resetting: " ^ Printexc.to_string e);
+ if coqtop.status <> Closed then respawn_coqtop coqtop
-let unsafe_process coqtop f =
- coqtop.is_computing <- true;
- try
- f coqtop.handle;
- coqtop.is_computing <- false;
- Mutex.unlock coqtop.lock
- with
- | DeadCoqtop ->
- coqtop.is_computing <- false;
- Mutex.lock toplvl_ctr_mtx;
- (* Coqtop died from an non natural cause, we have the lock, so it's our duty
- to relaunch it here. *)
- if not coqtop.is_closed && not coqtop.is_to_reset then begin
- ignore_error unsafe_clear_handle coqtop;
- let try_respawn () =
- coqtop.handle <- unsafe_spawn_handle coqtop.sup_args;
- in
- ignore_error try_respawn ();
- (* If respawning coqtop failed, there is not much we can do... *)
- assert (coqtop.handle.alive = true);
- (* Process the reset callback *)
- ignore_error coqtop.trigger coqtop.handle;
- end;
- Mutex.unlock toplvl_ctr_mtx;
- Mutex.unlock coqtop.lock;
- | err ->
- (* Another error occured, we propagate it. *)
- coqtop.is_computing <- false;
- Mutex.unlock coqtop.lock;
- raise err
-
-let grab coqtop f =
- Mutex.lock coqtop.lock;
- if not coqtop.is_closed && not coqtop.is_to_reset then unsafe_process coqtop f
- else Mutex.unlock coqtop.lock
-
-let try_grab coqtop f g =
- if Mutex.try_lock coqtop.lock then
- if not coqtop.is_closed && not coqtop.is_to_reset then unsafe_process coqtop f
- else Mutex.unlock coqtop.lock
- else g ()
+let try_grab coqtop task abort =
+ match coqtop.status with
+ |Closed -> ()
+ |Busy|New -> abort ()
+ |Ready -> process_task coqtop task
+
+let init_coqtop coqtop task =
+ assert (coqtop.status = New);
+ process_task coqtop task
(** * Calls to coqtop *)
(** Cf [Ide_intf] for more details *)
-let eval_call coqtop logger c =
- (** Retrieve the messages sent by coqtop until an answer has been received *)
- let rec loop () =
- let xml = Xml_parser.parse coqtop.xml_parser in
- if Serialize.is_message xml then
- let message = Serialize.to_message xml in
- let level = message.Interface.message_level in
- let content = message.Interface.message_content in
- let () = logger level content in
- loop ()
- else (Serialize.to_answer xml c)
- in
- try
- Xml_utils.print_xml coqtop.cin (Serialize.of_call c);
- flush coqtop.cin;
- loop ()
- with
- | Serialize.Marshal_error ->
- (* the protocol was not respected... *)
- raise Serialize.Marshal_error
- | err ->
- (* if anything else happens here, coqtop is most likely dead *)
- let msg = Printf.sprintf "Error communicating with pid [%i]: %s"
- coqtop.pid (Printexc.to_string err)
- in
- Minilib.log msg;
- raise DeadCoqtop
-
-let interp coqtop log ?(raw=false) ?(verbose=true) s =
- eval_call coqtop log (Serialize.interp (raw,verbose,s))
-let rewind coqtop i = eval_call coqtop default_logger (Serialize.rewind i)
-let inloadpath coqtop s = eval_call coqtop default_logger (Serialize.inloadpath s)
-let mkcases coqtop s = eval_call coqtop default_logger (Serialize.mkcases s)
-let status coqtop = eval_call coqtop default_logger Serialize.status
-let hints coqtop = eval_call coqtop default_logger Serialize.hints
-let search coqtop flags = eval_call coqtop default_logger (Serialize.search flags)
-
-let unsafe_close coqtop =
- if Mutex.try_lock coqtop.lock then begin
- let () =
- try
- match eval_call coqtop.handle default_logger Serialize.quit with
- | Interface.Good _ -> ()
- | _ -> raise Exit
- with err -> kill_coqtop coqtop
- in
- Mutex.unlock coqtop.lock
- end else begin
- (* bring me the chainsaw! *)
- kill_coqtop coqtop
- end
-
-let close_coqtop coqtop =
- (* wait for acquiring the process management lock *)
- atomically toplvl_ctr_mtx (fun _ -> coqtop.is_closed <- true) ();
- (* try to quit coqtop gently *)
- unsafe_close coqtop;
- (* Finalize the handle *)
- Mutex.lock coqtop.lock;
- Mutex.lock toplvl_ctr_mtx;
- ignore_error unsafe_clear_handle coqtop;
- Mutex.unlock toplvl_ctr_mtx;
- Mutex.unlock coqtop.lock
-
-let reset_coqtop coqtop hook =
- (* wait for acquiring the process management lock *)
- atomically toplvl_ctr_mtx (fun _ -> coqtop.is_to_reset <- true) ();
- (* try to quit coqtop gently *)
- unsafe_close coqtop;
- (* Reset the handle *)
- Mutex.lock coqtop.lock;
- Mutex.lock toplvl_ctr_mtx;
- ignore_error unsafe_clear_handle coqtop;
- let try_respawn () =
- coqtop.handle <- unsafe_spawn_handle coqtop.sup_args;
- in
- ignore_error try_respawn ();
- (* If respawning coqtop failed, there is not much we can do... *)
- assert (coqtop.handle.alive = true);
- (* Reset done *)
- coqtop.is_to_reset <- false;
- (* Process the reset callback with the given hook *)
- ignore_error hook coqtop.handle;
- Mutex.unlock toplvl_ctr_mtx;
- Mutex.unlock coqtop.lock
+type 'a atask = handle -> ('a Interface.value -> void) -> void
+
+let eval_call ?(logger=default_logger) call handle k =
+ (** Send messages to coqtop and prepare the decoding of the answer *)
+ Minilib.log ("Start eval_call " ^ Serialize.pr_call call);
+ assert (handle.alive && handle.waiting_for = None);
+ handle.waiting_for <- Some (mk_ccb (call,k), logger);
+ Xml_utils.print_xml handle.cin (Serialize.of_call call);
+ flush handle.cin;
+ Minilib.log "End eval_call";
+ Void
+
+let interp ?(logger=default_logger) ?(raw=false) ?(verbose=true) s =
+ eval_call ~logger (Serialize.interp (raw,verbose,s))
+let rewind i = eval_call (Serialize.rewind i)
+let inloadpath s = eval_call (Serialize.inloadpath s)
+let mkcases s = eval_call (Serialize.mkcases s)
+let status = eval_call Serialize.status
+let hints = eval_call Serialize.hints
+let search flags = eval_call (Serialize.search flags)
module PrintOpt =
struct
@@ -473,26 +508,27 @@ struct
let state_hack = Hashtbl.create 11
let _ = List.iter (fun opt -> Hashtbl.add state_hack opt false)
- [ implicit; coercions; raw_matching; notations; all_basic; existential; universes ]
-
- let set coqtop options =
- let () = List.iter (fun (name, v) -> Hashtbl.replace state_hack name v) options in
- let options = List.map (fun (name, v) -> (name, Interface.BoolValue v)) options in
- let options = (width, Interface.IntValue !width_ref):: options in
- match eval_call coqtop default_logger (Serialize.set_options options) with
- | Interface.Good () -> ()
- | _ -> raise (Failure "Cannot set options.")
-
- let enforce_hack coqtop =
- let elements = Hashtbl.fold (fun opt v acc -> (opt, v) :: acc) state_hack [] in
- set coqtop elements
+ [ implicit; coercions; raw_matching; notations;
+ all_basic; existential; universes ]
+
+ let set opts h k =
+ List.iter (fun (name, v) -> Hashtbl.replace state_hack name v) opts;
+ let opts = List.map (fun (n, v) -> (n, Interface.BoolValue v)) opts in
+ let opts = (width, Interface.IntValue !width_ref):: opts in
+ eval_call (Serialize.set_options opts) h
+ (function
+ | Interface.Good () -> k ()
+ | _ -> failwith "Cannot set options. Resetting coqtop")
+
+ let enforce_hack h k =
+ let elements = Hashtbl.fold (fun opt v acc -> (opt, v) :: acc) state_hack []
+ in
+ set elements h k
end
-let goals coqtop =
- let () = PrintOpt.enforce_hack coqtop in
- eval_call coqtop default_logger Serialize.goals
+let goals h k =
+ PrintOpt.enforce_hack h (fun () -> eval_call Serialize.goals h k)
-let evars coqtop =
- let () = PrintOpt.enforce_hack coqtop in
- eval_call coqtop default_logger Serialize.evars
+let evars h k =
+ PrintOpt.enforce_hack h (fun () -> eval_call Serialize.evars h k)
diff --git a/ide/coq.mli b/ide/coq.mli
index 43a040822..041485237 100644
--- a/ide/coq.mli
+++ b/ide/coq.mli
@@ -27,82 +27,104 @@ val check_connection : string list -> unit
(** * The structure describing a coqtop sub-process *)
(** Liveness management of coqtop is automatic. Whenever a Coqtop dies abruptly,
- this module is responsible for relaunching the whole process. The hook
+ this module is responsible for relaunching the whole process. The hook
passed as an argument in coqtop construction will be called after such an
- abrupt failure. In particular, it is NOT called when explicitely requesting
+ abrupt failure. In particular, it is NOT called when explicitely requesting
coqtop to close or to reset. *)
type coqtop
type handle
-(** Count of all active coqtops *)
-val coqtop_zombies : unit -> int
+(** * Coqtop tasks
+
+ A task is a group of sequential calls to be perform on a coqtop.
+ If a task is already sent to coqtop, it is considered busy
+ ([is_computing] will answer [true]), and other task submission
+ will be rejected.
+
+ A task is represented as a continuation, with a coqtop [handle]
+ as first argument, and a final inner continuation as 2nd argument.
+ This inner continuation should be runned at the end of the task.
+ Any exception occuring within the task will trigger a coqtop reset.
+*)
+
+type void
+type task = handle -> (unit->void) -> void
+
+(** Check if coqtop is computing, i.e. already has a current task *)
+val is_computing : coqtop -> bool
(** * Starting / signaling / ending a real coqtop sub-process *)
-(** Create a coqtop process out of a hook and some command-line arguments.
- The hook SHALL NOT use [grab] or its variants, otherwise you'll deadlock! *)
-val spawn_coqtop : (handle -> unit) -> string list -> coqtop
+(** Create a coqtop process with some command-line arguments. *)
+val spawn_coqtop : string list -> coqtop
-(** Interrupt the current computation of coqtop. Asynchronous. *)
+(** Register a handler called when a coqtop dies (badly or on purpose) *)
+type reset_kind = Planned | Unexpected
+val set_reset_handler : coqtop -> (reset_kind -> task) -> unit
+
+(** Finish initializing a freshly spawned coqtop, by running a first task on it.
+ The task should run its inner continuation at the end. *)
+val init_coqtop : coqtop -> task -> unit
+
+(** Interrupt the current computation of coqtop. *)
val break_coqtop : coqtop -> unit
(** Close coqtop. Subsequent requests will be discarded. Hook ignored. *)
val close_coqtop : coqtop -> unit
-(** Reset coqtop. Pending requests will be discarded. Default hook ignored,
- provided one used instead. *)
-val reset_coqtop : coqtop -> (handle -> unit) -> unit
+(** Reset coqtop. Pending requests will be discarded. The reset handler
+ of coqtop will be called with [Planned] as first argument *)
+val reset_coqtop : coqtop -> unit
-(** Last resort against a reluctant coqtop (a.k.a. chainsaw massacre).
- Asynchronous. *)
-val kill_coqtop : coqtop -> unit
+(** In win32, we'll use a different kill function than Unix.kill *)
-(** * Coqtop commmunication *)
+val killer : (int -> unit) ref
+val soft_killer : (int -> unit) ref
+val interrupter : (int -> unit) ref
-(** Start a coqtop dialogue and ensure that there is no interfering process.
- - If coqtop ever dies during the computation, this function restarts coqtop
- and calls the restart hook with the fresh coqtop.
- - If the argument function raises an exception, it is propagated.
- - The request may be discarded if a [close_coqtop] or [reset_coqtop] occurs
- before its completion.
-*)
-val grab : coqtop -> (handle -> unit) -> unit
+(** [set_final_countdown] triggers an exit of coqide after
+ some last cycles for closing remaining coqtop zombies *)
-(** As [grab], but applies the second callback if coqtop is already busy. Please
- consider using [try_grab] instead of [grab] except if you REALLY want
- something to happen. *)
-val try_grab : coqtop -> (handle -> unit) -> (unit -> unit) -> unit
+val final_countdown : unit -> unit
-(** Check if coqtop is computing. Does not take any lock. *)
-val is_computing : coqtop -> bool
+(** * Coqtop commmunication *)
-(** Check if coqtop is closed. Does not take any lock. *)
-val is_closed : coqtop -> bool
+(** Try to schedule a task on a coqtop. If coqtop is available, the task
+ callback is run (asynchronously), otherwise the [(unit->unit)] callback
+ is triggered.
+ - If coqtop ever dies during the computation, this function restarts coqtop
+ and calls the restart hook with the fresh coqtop.
+ - If the argument function raises an exception, a coqtop reset occurs.
+ - The task may be discarded if a [close_coqtop] or [reset_coqtop] occurs
+ before its completion.
+ - The task callback should run its inner continuation at the end. *)
-(** In win32, we'll use a different kill function than Unix.kill *)
+val try_grab : coqtop -> task -> (unit -> unit) -> unit
-val killer : (int -> unit) ref
-val interrupter : (int -> unit) ref
+(** * Atomic calls to coqtop
-(** * Calls to Coqtop, cf [Serialize] for more details *)
+ These atomic calls can be combined to form arbitrary multi-call tasks.
+ They correspond to the protocol calls (cf [Serialize] for more details).
+ Note that each call is asynchronous: it will return immediately,
+ but the inner callback will be executed later to handle the call answer
+ when this answer is available.
+ Except for interp, we use the default logger for any call. *)
-type logger = Interface.message_level -> string -> unit
-(** Except for interp, we use the default logger for any call. *)
+type 'a atask = handle -> ('a Interface.value -> void) -> void
-val interp :
- handle -> logger -> ?raw:bool -> ?verbose:bool -> string -> string Interface.value
-val rewind : handle -> int -> int Interface.value
-val status : handle -> Interface.status Interface.value
-val goals : handle -> Interface.goals option Interface.value
-val evars : handle -> Interface.evar list option Interface.value
-val hints : handle -> (Interface.hint list * Interface.hint) option Interface.value
-val inloadpath : handle -> string -> bool Interface.value
-val mkcases : handle -> string -> string list list Interface.value
-val search : handle -> Interface.search_flags -> string Interface.coq_object list Interface.value
+val interp : ?logger:Ideutils.logger -> ?raw:bool -> ?verbose:bool ->
+ string -> string atask
+val rewind : int -> int atask
+val status : Interface.status atask
+val goals : Interface.goals option atask
+val evars : Interface.evar list option atask
+val hints : (Interface.hint list * Interface.hint) option atask
+val inloadpath : string -> bool atask
+val mkcases : string -> string list list atask
+val search : Interface.search_flags -> string Interface.coq_object list atask
-(** A specialized version of [raw_interp] dedicated to
- set/unset options. *)
+(** A specialized version of [raw_interp] dedicated to set/unset options. *)
module PrintOpt :
sig
@@ -116,5 +138,5 @@ sig
val universes : t
val set_printing_width : int -> unit
- val set : handle -> (t * bool) list -> unit
+ val set : (t * bool) list -> task
end
diff --git a/ide/coqide.ml b/ide/coqide.ml
index 596ef25d9..f12ea9a24 100644
--- a/ide/coqide.ml
+++ b/ide/coqide.ml
@@ -60,7 +60,6 @@ let logfile = ref None
class type _analyzed_view =
object
-
method filename : string option
method update_stats : unit
method changed_on_disk : bool
@@ -68,22 +67,22 @@ object
method auto_save : unit
method save : string -> bool
method save_as : string -> bool
- method go_to_insert : Coq.handle -> unit
- method find_next_occurrence : direction -> unit
- method tactic_wizard : Coq.handle -> string list -> unit
- method insert_message : string -> unit
- method process_next_phrase : Coq.handle -> bool -> unit
- method process_until_end_or_error : Coq.handle -> unit
method recenter_insert : unit
- method erroneous_reset_initial : Coq.handle -> unit
- method requested_reset_initial : Coq.handle -> unit
+ method insert_message : string -> unit
method set_message : string -> unit
- method raw_coq_query : Coq.handle -> string -> unit
- method show_goals : Coq.handle -> unit
- method backtrack_last_phrase : Coq.handle -> unit
+ method find_next_occurrence : direction -> unit
method help_for_keyword : unit -> unit
-end
+ method go_to_insert : Coq.task
+ method tactic_wizard : string list -> Coq.task
+ method process_next_phrase : Coq.task
+ method process_until_end_or_error : Coq.task
+ method handle_reset_initial : Coq.reset_kind -> Coq.task
+ method raw_coq_query : string -> Coq.task
+ method show_goals : Coq.task
+ method backtrack_last_phrase : Coq.task
+ method include_file_dir_in_path : Coq.task
+end
type viewable_script = {
script : Wg_ScriptView.script_view;
@@ -182,25 +181,12 @@ exception Unsuccessful
let force_reset_initial () =
Minilib.log "Reset Initial";
- let term = session_notebook#current_term in
- Coq.reset_coqtop term.toplvl term.analyzed_view#requested_reset_initial
+ Coq.reset_coqtop session_notebook#current_term.toplvl
let break () =
Minilib.log "User break received";
Coq.break_coqtop session_notebook#current_term.toplvl
-let do_if_not_computing term text f =
- let threaded_task () =
- let info () = Minilib.log ("Discarded query:" ^ text) in
- try Coq.try_grab term.toplvl f info
- with
- | e ->
- let msg = "Unknown error, please report:\n" ^ (Printexc.to_string e) in
- term.analyzed_view#set_message msg
- in
- Minilib.log ("Launching thread " ^ text);
- ignore (Thread.create threaded_task ())
-
let warn_image =
let img = GMisc.image () in
img#set_stock `DIALOG_WARNING;
@@ -409,7 +395,7 @@ object(self)
let do_revert f =
push_info "Reverting buffer";
try
- Coq.reset_coqtop mycoqtop self#requested_reset_initial;
+ Coq.reset_coqtop mycoqtop;
let b = Buffer.create 1024 in
Ideutils.read_file f b;
let s = try_convert (Buffer.contents b) in
@@ -520,16 +506,11 @@ object(self)
input_buffer#get_iter_at_mark `INSERT
method recenter_insert =
- (* BUG : to investigate further:
- FIXED : Never call GMain.* in thread !
- PLUS : GTK BUG ??? Cannot be called from a thread...
- ADDITION: using sync instead of async causes deadlock...*)
- ignore (GtkThread.async (
- input_view#scroll_to_mark
- ~use_align:false
- ~yalign:0.75
- ~within_margin:0.25)
- `INSERT)
+ input_view#scroll_to_mark
+ ~use_align:false ~yalign:0.75 ~within_margin:0.25 `INSERT
+
+ method help_for_keyword () =
+ browse_keyword (self#insert_message) (get_current_word ())
(* go to the next occurrence of the current word, forward or backward *)
method find_next_occurrence dir =
@@ -546,32 +527,34 @@ object(self)
(b#place_cursor start;
self#recenter_insert)
- method show_goals handle =
+ method show_goals h k =
Coq.PrintOpt.set_printing_width proof_view#width;
- match Coq.goals handle with
- | Interface.Fail (l, str) ->
- self#set_message ("Error in coqtop:\n"^str)
- | Interface.Good goals | Interface.Unsafe goals ->
- begin match Coq.evars handle with
- | Interface.Fail (l, str)->
- self#set_message ("Error in coqtop:\n"^str)
- | Interface.Good evs | Interface.Unsafe evs ->
- proof_view#set_goals goals;
- proof_view#set_evars evs;
- proof_view#refresh ()
- end
+ Coq.goals h (function
+ |Interface.Fail (l, str) ->
+ (self#set_message ("Error in coqtop:\n"^str); k())
+ |Interface.Good goals | Interface.Unsafe goals ->
+ Coq.evars h (function
+ |Interface.Fail (l, str)->
+ (self#set_message ("Error in coqtop:\n"^str); k())
+ |Interface.Good evs | Interface.Unsafe evs ->
+ proof_view#set_goals goals;
+ proof_view#set_evars evs;
+ proof_view#refresh ();
+ k()))
(* This method is intended to perform stateless commands *)
- method raw_coq_query handle phrase =
+ method raw_coq_query phrase h k =
let () = Minilib.log "raw_coq_query starting now" in
let display_error s =
if not (Glib.Utf8.validate s) then
flash_info "This error is so nasty that I can't even display it."
else self#insert_message s;
in
- match Coq.interp handle self#push_message ~raw:true ~verbose:false phrase with
- | Interface.Fail (_, err) -> sync display_error err
- | Interface.Good msg | Interface.Unsafe msg -> sync self#insert_message msg
+ Coq.interp ~logger:self#push_message ~raw:true ~verbose:false phrase h
+ (function
+ | Interface.Fail (_, err) -> display_error err; k ()
+ | Interface.Good msg | Interface.Unsafe msg ->
+ self#insert_message msg; k ())
method private find_phrase_starting_at (start:GText.iter) =
try
@@ -608,51 +591,7 @@ object(self)
in
try loop 0 self#get_start_of_input with Exit -> ()
- method private process_command_queue ?(verbose = false) queue handle =
- let error = ref None in
- let info = ref [] in
- let current_flags = ref [] in
- let push_info level message = info := (level, message) :: !info in
- (* First, process until error *)
- Minilib.log "Begin command processing";
- while not (Queue.is_empty queue) && !error = None do
- let sentence = Queue.peek queue in
- (* If the line is not a comment, we interpret it. *)
- if not (List.mem `COMMENT sentence.flags) then begin
- let start = input_buffer#get_iter_at_mark sentence.start in
- let stop = input_buffer#get_iter_at_mark sentence.stop in
- let phrase = start#get_slice ~stop in
- match Coq.interp handle push_info ~verbose phrase with
- | Interface.Fail (loc, msg) ->
- error := Some (phrase, loc, msg);
- | Interface.Good msg ->
- push_info Interface.Notice msg;
- current_flags := []
- | Interface.Unsafe msg ->
- current_flags := [`UNSAFE]
- end;
- (* If there is no error, then we mark it done *)
- if !error = None then begin
- (* We reget the iters here because Gtk is unable to warranty that they
- were not modified meanwhile. Not really necessary but who knows... *)
- let start = input_buffer#get_iter_at_mark sentence.start in
- let stop = input_buffer#get_iter_at_mark sentence.stop in
- let sentence = { sentence with
- flags = !current_flags @ sentence.flags
- } in
- let tag =
- if List.mem `UNSAFE !current_flags then Tags.Script.unjustified
- else Tags.Script.processed
- in
- input_buffer#move_mark ~where:stop (`NAME "start_of_input");
- input_buffer#apply_tag tag ~start ~stop;
- input_buffer#remove_tag Tags.Script.to_process ~start ~stop;
- (* Discard the old stack info and put it were it belongs *)
- ignore (Queue.pop queue);
- Stack.push sentence cmd_stack;
- end;
- done;
- (* Then clear all irrelevant commands *)
+ method private discard_command_queue queue =
while not (Queue.is_empty queue) do
let sentence = Queue.pop queue in
let start = input_buffer#get_iter_at_mark sentence.start in
@@ -660,25 +599,46 @@ object(self)
input_buffer#remove_tag Tags.Script.to_process ~start ~stop;
input_buffer#delete_mark sentence.start;
input_buffer#delete_mark sentence.stop;
- done;
- (* Return the list of info messages and the error *)
- (List.rev !info, !error)
+ done
- method private show_error phrase loc msg = match loc with
- | None ->
- message_view#clear ();
- message_view#push Interface.Error msg
- | Some (start, stop) ->
- let soi = self#get_start_of_input in
- let start = soi#forward_chars (byte_offset_to_char_offset phrase start) in
- let stop = soi#forward_chars (byte_offset_to_char_offset phrase stop) in
- input_buffer#apply_tag Tags.Script.error ~start ~stop;
- input_buffer#place_cursor ~where:start;
+ method private commit_queue_transaction queue sentence newflags =
+ (* A queued command has been successfully done, we push it to [cmd_stack].
+ We reget the iters here because Gtk is unable to warranty that they
+ were not modified meanwhile. Not really necessary but who knows... *)
+ let start = input_buffer#get_iter_at_mark sentence.start in
+ let stop = input_buffer#get_iter_at_mark sentence.stop in
+ let sentence = { sentence with flags = newflags @ sentence.flags } in
+ let tag =
+ if List.mem `UNSAFE newflags then Tags.Script.unjustified
+ else Tags.Script.processed
+ in
+ input_buffer#move_mark ~where:stop (`NAME "start_of_input");
+ input_buffer#apply_tag tag ~start ~stop;
+ input_buffer#remove_tag Tags.Script.to_process ~start ~stop;
+ ignore (Queue.pop queue);
+ Stack.push sentence cmd_stack
+
+ method private process_error queue phrase loc msg h k =
+ let position_error = function
+ | None -> ()
+ | Some (start, stop) ->
+ let soi = self#get_start_of_input in
+ let start =
+ soi#forward_chars (byte_offset_to_char_offset phrase start) in
+ let stop =
+ soi#forward_chars (byte_offset_to_char_offset phrase stop) in
+ input_buffer#apply_tag Tags.Script.error ~start ~stop;
+ input_buffer#place_cursor ~where:start
+ in
+ self#discard_command_queue queue;
+ pop_info ();
+ position_error loc;
message_view#clear ();
- message_view#push Interface.Error msg
+ message_view#push Interface.Error msg;
+ self#show_goals h k
(** Compute the phrases until [until] returns [true]. *)
- method private process_until until finish handle verbose =
+ method private process_until until verbose h k =
let queue = Queue.create () in
(* Lock everything and fill the waiting queue *)
push_info "Coq is computing";
@@ -687,37 +647,54 @@ object(self)
self#fill_command_queue until queue;
(* Now unlock and process asynchronously *)
input_view#set_editable true;
- let (msg, error) = self#process_command_queue ~verbose queue handle in
- pop_info ();
- (* Display the goal and any error *)
- self#show_goals handle;
- match error with
- | None ->
- if verbose then
- List.iter (fun (lvl, msg) -> self#push_message lvl msg) msg;
- finish ();
- self#recenter_insert
- | Some (phrase, loc, msg) ->
- self#show_error phrase loc msg
-
- method process_next_phrase handle verbose =
+ let push_info lvl msg = if verbose then self#push_message lvl msg
+ in
+ Minilib.log "Begin command processing";
+ let rec loop () =
+ if Queue.is_empty queue then
+ let () = pop_info () in
+ let () = self#recenter_insert in
+ self#show_goals h k
+ else
+ let sentence = Queue.peek queue in
+ if List.mem `COMMENT sentence.flags then
+ (self#commit_queue_transaction queue sentence []; loop ())
+ else
+ (* If the line is not a comment, we interpret it. *)
+ let start = input_buffer#get_iter_at_mark sentence.start in
+ let stop = input_buffer#get_iter_at_mark sentence.stop in
+ let phrase = start#get_slice ~stop in
+ let commit_and_continue msg flags =
+ push_info Interface.Notice msg;
+ self#commit_queue_transaction queue sentence flags;
+ loop ()
+ in
+ Coq.interp ~logger:push_info ~verbose phrase h
+ (function
+ |Interface.Good msg -> commit_and_continue msg []
+ |Interface.Unsafe msg -> commit_and_continue msg [`UNSAFE]
+ |Interface.Fail (loc, msg) ->
+ self#process_error queue phrase loc msg h k)
+ in
+ loop ()
+
+ method process_next_phrase h k =
let until len start stop = 1 <= len in
- let finish () = input_buffer#place_cursor self#get_start_of_input in
- self#process_until until finish handle verbose
+ self#process_until until true h
+ (fun () -> input_buffer#place_cursor ~where:self#get_start_of_input; k())
- method private process_until_iter handle iter =
+ method private process_until_iter iter h k =
let until len start stop =
if prefs.stop_before then stop#compare iter > 0
else start#compare iter >= 0
in
- let finish () = () in
- self#process_until until finish handle false
+ self#process_until until false h k
- method process_until_end_or_error handle =
- self#process_until_iter handle input_buffer#end_iter
+ method process_until_end_or_error h k =
+ self#process_until_iter input_buffer#end_iter h k
- (** Clear the command stack until [until] returns [true]. Returns the number
- of commands sent to Coqtop to backtrack. *)
+ (** Clear the command stack until [until] returns [true].
+ Returns the number of commands sent to Coqtop to backtrack. *)
method private clear_command_stack until =
let rec loop len real_len =
if Stack.is_empty cmd_stack then real_len
@@ -741,73 +718,57 @@ object(self)
loop 0 0
(** Actually performs the undoing *)
- method private undo_command_stack handle n =
- match Coq.rewind handle n with
- | Interface.Good n | Interface.Unsafe n ->
- let until _ len _ _ = n <= len in
- (* Coqtop requested [n] more ACTUAL backtrack *)
- ignore (self#clear_command_stack until)
- | Interface.Fail (l, str) ->
- self#set_message
- ("Error while backtracking: " ^ str ^
- "\nCoqIDE and coqtop may be out of sync, you may want to use Restart.")
+ method private undo_command_stack n h k =
+ Coq.rewind n h (function
+ |Interface.Good n | Interface.Unsafe n ->
+ let until _ len _ _ = n <= len in
+ (* Coqtop requested [n] more ACTUAL backtrack *)
+ ignore (self#clear_command_stack until);
+ k ()
+ |Interface.Fail (l, str) ->
+ self#set_message
+ ("Error while backtracking: " ^ str ^
+ "\nCoqIDE and coqtop may be out of sync," ^
+ "you may want to use Restart.");
+ k ())
(** Wrapper around the raw undo command *)
- method private backtrack_until until finish handle =
+ method private backtrack_until until h k =
push_info "Coq is undoing";
message_view#clear ();
(* Lock everything *)
input_view#set_editable false;
let to_undo = self#clear_command_stack until in
- self#undo_command_stack handle to_undo;
- input_view#set_editable true;
- pop_info ();
- finish ()
+ self#undo_command_stack to_undo h
+ (fun () -> input_view#set_editable true; pop_info (); k ())
- method private backtrack_to_iter handle iter =
+ method private backtrack_to_iter iter h k =
let until _ _ _ stop = iter#compare stop >= 0 in
- let finish () = () in
- self#backtrack_until until finish handle;
- (* We may have backtracked too much: let's replay *)
- self#process_until_iter handle iter
+ self#backtrack_until until h
+ (* We may have backtracked too much: let's replay *)
+ (fun () -> self#process_until_iter iter h k)
- method backtrack_last_phrase handle =
+ method backtrack_last_phrase h k =
let until len _ _ _ = 1 <= len in
- let finish () = input_buffer#place_cursor self#get_start_of_input in
- self#backtrack_until until finish handle;
- self#show_goals handle
+ self#backtrack_until until h
+ (fun () ->
+ input_buffer#place_cursor ~where:self#get_start_of_input;
+ self#show_goals h k)
- method go_to_insert handle =
+ method go_to_insert h k =
let point = self#get_insert in
if point#compare self#get_start_of_input >= 0
- then self#process_until_iter handle point
- else self#backtrack_to_iter handle point
+ then self#process_until_iter point h k
+ else self#backtrack_to_iter point h k
- method private send_to_coq handle phrase =
- let display_error (loc, s) =
- if not (Glib.Utf8.validate s) then
- flash_info "This error is so nasty that I can't even display it."
- else self#insert_message s
- in
- Minilib.log "Send_to_coq starting now";
- match Coq.interp handle default_logger ~verbose:false phrase with
- | Interface.Fail (l, str) -> sync display_error (l, str); None
- | Interface.Good msg -> sync self#insert_message msg; Some []
- | Interface.Unsafe msg -> sync self#insert_message msg; Some [`UNSAFE]
-
- method private insert_this_phrase_on_success handle coqphrase insertphrase =
- let mark_processed flags =
+ method tactic_wizard l h k =
+ let insert_phrase phrase tag =
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 phrase' = if stop#starts_line then phrase else "\n"^phrase in
+ input_buffer#insert ~iter:stop phrase';
tag_on_insert (input_buffer :> GText.buffer);
let start = self#get_start_of_input in
input_buffer#move_mark ~where:stop (`NAME "start_of_input");
- let tag =
- if List.mem `UNSAFE flags then Tags.Script.unjustified
- else Tags.Script.processed
- in
input_buffer#apply_tag tag ~start ~stop;
if self#get_insert#compare stop <= 0 then
input_buffer#place_cursor ~where:stop;
@@ -817,16 +778,38 @@ object(self)
flags = [];
} in
Stack.push ide_payload cmd_stack;
- self#show_goals handle;
+ message_view#clear ();
+ self#show_goals h k;
in
- match self#send_to_coq handle coqphrase with
- | Some flags -> sync mark_processed flags; true
- | None ->
- sync (fun _ -> self#insert_message ("Unsuccessfully tried: "^coqphrase)) ();
- false
+ let display_error (loc, s) =
+ if not (Glib.Utf8.validate s) then
+ flash_info "This error is so nasty that I can't even display it."
+ else self#insert_message s
+ in
+ let try_phrase phrase stop more =
+ Minilib.log "Sending to coq now";
+ Coq.interp ~verbose:false phrase h
+ (function
+ |Interface.Fail (l, str) ->
+ display_error (l, str);
+ self#insert_message ("Unsuccessfully tried: "^phrase);
+ more ()
+ |Interface.Good msg ->
+ self#insert_message msg;
+ stop Tags.Script.processed
+ |Interface.Unsafe msg ->
+ self#insert_message msg;
+ stop Tags.Script.unjustified)
+ in
+ let rec loop l () = match l with
+ | [] -> k ()
+ | p :: l' ->
+ try_phrase ("progress "^p^".") (insert_phrase (p^".")) (loop l')
+ in
+ loop l ()
- method private generic_reset_initial handle =
- let start = input_buffer#start_iter in
+ method handle_reset_initial why h k =
+ if why = Coq.Unexpected then warning "Coqtop died badly. Resetting.";
(* clear the stack *)
while not (Stack.is_empty cmd_stack) do
let phrase = Stack.pop cmd_stack in
@@ -834,52 +817,39 @@ object(self)
input_buffer#delete_mark phrase.stop
done;
(* reset the buffer *)
+ let start = input_buffer#start_iter in
+ let stop = input_buffer#end_iter in
input_buffer#move_mark ~where:start (`NAME "start_of_input");
- input_buffer#remove_tag Tags.Script.processed start input_buffer#end_iter;
- input_buffer#remove_tag Tags.Script.unjustified start input_buffer#end_iter;
- input_buffer#remove_tag Tags.Script.to_process start input_buffer#end_iter;
+ input_buffer#remove_tag Tags.Script.processed ~start ~stop;
+ input_buffer#remove_tag Tags.Script.unjustified ~start ~stop;
+ input_buffer#remove_tag Tags.Script.to_process ~start ~stop;
tag_on_insert (input_buffer :> GText.buffer);
(* clear the views *)
message_view#clear ();
proof_view#clear ();
+ clear_info ();
+ push_info "Restarted";
(* apply the initial commands to coq *)
- self#include_file_dir_in_path handle;
-
- method erroneous_reset_initial handle =
- self#generic_reset_initial handle;
- (* warn the user *)
- warning "Coqtop died badly. Resetting."
-
- method requested_reset_initial handle =
- self#generic_reset_initial handle
-
- method tactic_wizard handle l =
- async message_view#clear ();
- ignore
- (List.exists
- (fun p ->
- self#insert_this_phrase_on_success handle
- ("progress "^p^".") (p^".")) l)
+ self#include_file_dir_in_path h k;
- method private include_file_dir_in_path handle =
+ method include_file_dir_in_path h k =
match filename with
- | None -> ()
- | Some f ->
- let dir = Filename.dirname f in
- match Coq.inloadpath handle dir with
- | Interface.Fail (_,s) ->
- self#set_message
- ("Could not determine lodpath, this might lead to problems:\n"^s)
- | Interface.Good true | Interface.Unsafe true -> ()
- | Interface.Good false | Interface.Unsafe false ->
- let cmd = Printf.sprintf "Add LoadPath \"%s\". " dir in
- match Coq.interp handle default_logger cmd with
- | Interface.Fail (l,str) ->
- self#set_message ("Couln't add loadpath:\n"^str)
- | Interface.Good _ | Interface.Unsafe _ -> ()
-
- method help_for_keyword () =
- browse_keyword (self#insert_message) (get_current_word ())
+ |None -> k ()
+ |Some f ->
+ let dir = Filename.dirname f in
+ Coq.inloadpath dir h (function
+ |Interface.Fail (_,s) ->
+ self#set_message
+ ("Could not determine lodpath, this might lead to problems:\n"^s);
+ k ()
+ |Interface.Good true |Interface.Unsafe true -> k ()
+ |Interface.Good false |Interface.Unsafe false ->
+ let cmd = Printf.sprintf "Add LoadPath \"%s\". " dir in
+ Coq.interp cmd h (function
+ |Interface.Fail (l,str) ->
+ self#set_message ("Couln't add loadpath:\n"^str);
+ k ()
+ |Interface.Good _ | Interface.Unsafe _ -> k ()))
(** NB: Events during text edition:
@@ -950,17 +920,25 @@ object(self)
| Some "insert" -> ()
| Some s -> Minilib.log (s^" moved")
| None -> ())
- in
- Coq.grab mycoqtop self#include_file_dir_in_path;
+ in ()
end
-let last_make = ref "";;
-let last_make_index = ref 0;;
+(** [last_make_buf] contains the output of the last make compilation.
+ [last_make] is the same, but as a string, refreshed only when searching
+ the next error.
+*)
+
+let last_make_buf = Buffer.create 1024
+let last_make = ref ""
+let last_make_index = ref 0
+let last_make_dir = ref ""
let search_compile_error_regexp =
Str.regexp
- "File \"\\([^\"]+\\)\", line \\([0-9]+\\), characters \\([0-9]+\\)-\\([0-9]+\\)";;
+ "File \"\\([^\"]+\\)\", line \\([0-9]+\\), characters \\([0-9]+\\)-\\([0-9]+\\)"
let search_next_error () =
+ if String.length !last_make <> Buffer.length last_make_buf
+ then last_make := Buffer.contents last_make_buf;
let _ =
Str.search_forward search_compile_error_regexp !last_make !last_make_index
in
@@ -971,80 +949,77 @@ let search_next_error () =
and msg_index = Str.match_beginning ()
in
last_make_index := Str.group_end 4;
- (f,l,b,e,
+ (Filename.concat !last_make_dir f, l, b, e,
String.sub !last_make msg_index (String.length !last_make - msg_index))
-
(**********************************************************************)
(* session creation and primitive handling *)
(**********************************************************************)
let create_session file =
- let script_buffer =
- GSourceView2.source_buffer
- ~tag_table:Tags.Script.table
- ~highlight_matching_brackets:true
- ?language:(lang_manager#language prefs.source_language)
- ?style_scheme:(style_manager#style_scheme prefs.source_style)
- ()
- in
- let proof = Wg_ProofView.proof_view () in
- let message = Wg_MessageView.message_view () in
let basename = match file with
- |None -> "*scratch*"
- |Some f -> Glib.Convert.filename_to_utf8 (Filename.basename f)
- in
- let get_args f =
- Project_file.args_from_project f
- !custom_project_files prefs.project_file_name
+ |None -> "*scratch*"
+ |Some f -> Glib.Convert.filename_to_utf8 (Filename.basename f)
in
let coqtop_args = match file with
|None -> !sup_args
- |Some the_file -> match prefs.read_project with
+ |Some the_file ->
+ let get_args f = Project_file.args_from_project f
+ !custom_project_files prefs.project_file_name
+ in
+ match prefs.read_project with
|Ignore_args -> !sup_args
|Append_args -> get_args the_file @ !sup_args
|Subst_args -> get_args the_file
in
- let reset = ref (fun _ -> ()) in
- let trigger handle = !reset handle in
- let ct = Coq.spawn_coqtop trigger coqtop_args in
- let script =
- Wg_ScriptView.script_view ct
- ~source_buffer:script_buffer
- ~show_line_numbers:true
- ~wrap_mode:`NONE () in
- let command = new Wg_Command.command_window ct in
- let finder = new Wg_Find.finder (script :> GText.view) in
- let legacy_av = new analyzed_view script proof message ct file in
- let () = reset := legacy_av#erroneous_reset_initial in
- let () = legacy_av#update_stats in
- let _ =
- script#buffer#create_mark ~name:"start_of_input" script#buffer#start_iter
+ let coqtop = Coq.spawn_coqtop coqtop_args
in
- let _ =
- script#buffer#create_mark ~name:"prev_insert" script#buffer#start_iter in
+ let buffer = GSourceView2.source_buffer
+ ~tag_table:Tags.Script.table
+ ~highlight_matching_brackets:true
+ ?language:(lang_manager#language prefs.source_language)
+ ?style_scheme:(style_manager#style_scheme prefs.source_style)
+ ()
+ in
+ let _ = buffer#create_mark ~name:"start_of_input" buffer#start_iter in
+ let _ = buffer#create_mark ~name:"prev_insert" buffer#start_iter in
+ let _ = buffer#place_cursor ~where:buffer#start_iter
+ in
+ let script = Wg_ScriptView.script_view coqtop ~source_buffer:buffer
+ ~show_line_numbers:true ~wrap_mode:`NONE ()
+ in
+ let _ = script#misc#set_name "ScriptWindow"
+ in
+ let proof = Wg_ProofView.proof_view () in
+ let _ = proof#misc#set_can_focus true in
let _ =
GtkBase.Widget.add_events proof#as_widget [`ENTER_NOTIFY;`POINTER_MOTION]
in
- let () =
- let fold accu (opts, _, _, _, dflt) =
- List.fold_left (fun accu opt -> (opt, dflt) :: accu) accu opts
- in
- let options = List.fold_left fold [] print_items in
- Coq.grab ct (fun handle -> Coq.PrintOpt.set handle options)
+ let message = Wg_MessageView.message_view () in
+ let _ = message#misc#set_can_focus true
+ in
+ let command = new Wg_Command.command_window coqtop in
+ let finder = new Wg_Find.finder (script :> GText.view) in
+ let view = new analyzed_view script proof message coqtop file in
+ let _ = Coq.set_reset_handler coqtop view#handle_reset_initial in
+ let _ = view#update_stats in
+ let _ = Coq.init_coqtop coqtop
+ (fun h k ->
+ view#include_file_dir_in_path h
+ (fun () ->
+ let fold accu (opts, _, _, _, dflt) =
+ List.fold_left (fun accu opt -> (opt, dflt) :: accu) accu opts
+ in
+ let options = List.fold_left fold [] print_items in
+ Coq.PrintOpt.set options h k))
in
- script#misc#set_name "ScriptWindow";
- script#buffer#place_cursor ~where:script#buffer#start_iter;
- proof#misc#set_can_focus true;
- message#misc#set_can_focus true;
-
{ tab_label= GMisc.label ~text:basename ();
script=script;
proof_view=proof;
message_view=message;
- analyzed_view=legacy_av;
- toplvl=ct;
+ analyzed_view=view;
+ toplvl=coqtop;
command=command;
finder=finder;
}
@@ -1065,9 +1040,6 @@ let pr_exit_status = function
| Unix.WEXITED 0 -> " succeeded"
| _ -> " failed"
-let run_command av cmd =
- CUnix.run_command Ideutils.try_convert av#insert_message cmd
-
(** Auxiliary functions for the File operations *)
module FileAux = struct
@@ -1152,37 +1124,12 @@ let check_quit saveall =
| 2 -> ()
| _ -> raise DontQuit
end;
- let wait_w = GWindow.window ~modal:true ~wm_class:"CoqIde" ~wm_name:"CoqIde"
- ~kind:`POPUP ~title:"Terminating coqtops" () in
- let _ =
- GMisc.label ~text:"Terminating coqtops processes, please wait ..."
- ~packing:wait_w#add ()
- in
- let warn_w = GWindow.message_dialog ~message_type:`WARNING
- ~buttons:GWindow.Buttons.yes_no
- ~message:
- ("Some coqtops processes are still running.\n" ^
- "If you quit CoqIDE right now, you may have to kill them manually.\n" ^
- "Do you want to wait for those processes to terminate ?")
- ()
- in
- let () =
- List.iter (fun _ -> session_notebook#remove_page 0) session_notebook#pages
- in
- let nb_try = ref 0 in
- let () = wait_w#show () in
- let () =
- while (Coq.coqtop_zombies () <> 0) && (!nb_try <= 50) do
- incr nb_try;
- Thread.delay 0.1 ;
- done
- in
- if !nb_try = 50 then begin
- wait_w#misc#hide ();
- match warn_w#run () with
- | `YES -> warn_w#misc#hide (); raise DontQuit
- | `NO | `DELETE_EVENT -> ()
- end
+ List.iter (fun p -> Coq.close_coqtop p.toplvl) session_notebook#pages
+
+(* For MacOS, just to be sure, we close all coqtops (again?) *)
+let close_and_quit () =
+ List.iter (fun p -> Coq.close_coqtop p.toplvl) session_notebook#pages;
+ Coq.final_countdown ()
let crash_save exitcode =
Minilib.log "Starting emergency save of buffers in .crashcoqide files";
@@ -1246,7 +1193,8 @@ let revert_all _ =
session_notebook#pages
let quit _ =
- try FileAux.check_quit saveall; exit 0 with FileAux.DontQuit -> ()
+ try FileAux.check_quit saveall; Coq.final_countdown ()
+ with FileAux.DontQuit -> ()
let close_buffer _ =
let do_remove () =
@@ -1282,11 +1230,13 @@ let export kind _ =
| _ -> assert false
in
let cmd =
- local_cd f ^ prefs.cmd_coqdoc ^ " --" ^ kind ^
- " -o " ^ (Filename.quote output) ^ " " ^ (Filename.quote basef)
+ local_cd f ^ prefs.cmd_coqdoc ^ " --" ^ kind ^ " -o " ^
+ (Filename.quote output) ^ " " ^ (Filename.quote basef) ^ " 2>&1"
in
- let st,_ = run_command av cmd in
- flash_info (cmd ^ pr_exit_status st)
+ av#set_message ("Running: "^cmd);
+ let finally st = flash_info (cmd ^ pr_exit_status st)
+ in
+ run_command av#insert_message finally cmd
let print _ =
let av = current_view () in
@@ -1315,13 +1265,13 @@ let print _ =
let ok = GButton.button ~stock:`PRINT ~label:"Print" ~packing:h#add ()
in
let callback_print () =
+ w#destroy ();
let cmd = e#text in
- let st,_ = run_command av cmd in
- flash_info (cmd ^ pr_exit_status st);
- w#destroy ()
+ let finally st = flash_info (cmd ^ pr_exit_status st) in
+ run_command ignore finally cmd
in
- ignore (ko#connect#clicked ~callback:w#destroy);
- ignore (ok#connect#clicked ~callback:callback_print);
+ let _ = ko#connect#clicked ~callback:w#destroy in
+ let _ = ok#connect#clicked ~callback:callback_print in
w#misc#show ()
let highlight _ =
@@ -1349,10 +1299,11 @@ let reset_autosave_timer () =
(** Export of functions used in [coqide_main] : *)
-let forbid_quit_to_save () =
+let forbid_quit () =
try FileAux.check_quit File.saveall; false
with FileAux.DontQuit -> true
+let close_and_quit = FileAux.close_and_quit
let crash_save = FileAux.crash_save
let do_load f = FileAux.load_file f
@@ -1367,33 +1318,45 @@ let compile _ =
match av#filename with
|None -> flash_info "Active buffer has no name"
|Some f ->
- let cmd = prefs.cmd_coqc ^ " -I "
- ^ (Filename.quote (Filename.dirname f))
- ^ " " ^ (Filename.quote f) in
- let st,res = run_command av cmd in
- if st = Unix.WEXITED 0 then
- flash_info (f ^ " successfully compiled")
- else begin
- flash_info (f ^ " failed to compile");
- Coq.try_grab v.toplvl av#process_until_end_or_error ignore;
- av#insert_message "Compilation output:\n";
- av#insert_message res
- end
+ let cmd = prefs.cmd_coqc ^ " -I " ^ (Filename.quote (Filename.dirname f))
+ ^ " " ^ (Filename.quote f) ^ " 2>&1"
+ in
+ let buf = Buffer.create 1024 in
+ av#set_message ("Running: "^cmd);
+ let display s =
+ av#insert_message s;
+ Buffer.add_string buf s
+ in
+ let finally st =
+ if st = Unix.WEXITED 0 then
+ flash_info (f ^ " successfully compiled")
+ else begin
+ flash_info (f ^ " failed to compile");
+ av#set_message "Compilation output:\n";
+ av#insert_message (Buffer.contents buf);
+ end
+ in
+ run_command display finally cmd
let make _ =
let av = current_view () in
match av#filename with
|None -> flash_info "Cannot make: this buffer has no name"
|Some f ->
- let cmd = local_cd f ^ prefs.cmd_make in
- (*
- save_f ();
- *)
- av#insert_message "Command output:\n";
- let st,res = run_command av cmd in
- last_make := res;
+ File.saveall ();
+ let cmd = local_cd f ^ prefs.cmd_make ^ " 2>&1" in
+ av#set_message "Compilation output:\n";
+ Buffer.reset last_make_buf;
+ last_make := "";
last_make_index := 0;
- flash_info (prefs.cmd_make ^ pr_exit_status st)
+ last_make_dir := Filename.dirname f;
+ let display s =
+ av#insert_message s;
+ Buffer.add_string last_make_buf s
+ in
+ let finally st = flash_info (current.cmd_make ^ pr_exit_status st)
+ in
+ run_command display finally cmd
let next_error _ =
try
@@ -1410,9 +1373,7 @@ let next_error _ =
v.script#misc#grab_focus ()
with Not_found ->
last_make_index := 0;
- let v = session_notebook#current_term in
- let av = v.analyzed_view in
- av#set_message "No more errors.\n"
+ (current_view ())#set_message "No more errors.\n"
let coq_makefile _ =
let av = current_view () in
@@ -1420,8 +1381,9 @@ let coq_makefile _ =
|None -> flash_info "Cannot make makefile: this buffer has no name"
|Some f ->
let cmd = local_cd f ^ prefs.cmd_coqmakefile in
- let st,res = run_command av cmd in
- flash_info (prefs.cmd_coqmakefile ^ pr_exit_status st)
+ let finally st = flash_info (current.cmd_coqmakefile ^ pr_exit_status st)
+ in
+ run_command ignore finally cmd
let editor _ =
let av = current_view () in
@@ -1431,61 +1393,55 @@ let editor _ =
File.save ();
let f = Filename.quote f in
let cmd = Util.subst_command_placeholder prefs.cmd_editor f in
- let _ = run_command av cmd in
- av#revert
+ run_command ignore (fun _ -> av#revert) cmd
end
(** Callbacks for the Navigation menu *)
-let do_or_activate f =
- let p = session_notebook#current_term in
- do_if_not_computing p "do_or_activate"
- (fun handle ->
- let av = p.analyzed_view in
- f handle av;
- pop_info ();
- let msg = match Coq.status handle with
- | Interface.Fail (l, str) ->
- "Oops, problem while fetching coq status."
- | Interface.Good status | Interface.Unsafe status ->
- let path = match status.Interface.status_path with
- | [] | _ :: [] -> "" (* Drop the topmost level, usually "Top" *)
- | _ :: l -> " in " ^ String.concat "." l
- in
- let name = match status.Interface.status_proofname with
- | None -> ""
- | Some n -> ", proving " ^ n
- in
- "Ready" ^ path ^ name
+let update_status h k =
+ let display msg = pop_info (); push_info msg
+ in
+ Coq.status h (function
+ |Interface.Fail (l, str) ->
+ display "Oops, problem while fetching coq status."; k ()
+ |Interface.Good status | Interface.Unsafe status ->
+ let path = match status.Interface.status_path with
+ | [] | _ :: [] -> "" (* Drop the topmost level, usually "Top" *)
+ | _ :: l -> " in " ^ String.concat "." l
+ in
+ let name = match status.Interface.status_proofname with
+ | None -> ""
+ | Some n -> ", proving " ^ n
in
- push_info msg)
+ display ("Ready" ^ path ^ name); k ())
-let do_if_active f =
- let p = session_notebook#current_term in
- do_if_not_computing p "do_if_active"
- (fun handle -> ignore (f handle p.analyzed_view))
+let send_to_coq f =
+ let term = session_notebook#current_term in
+ let av = term.analyzed_view in
+ let info () = Minilib.log ("Coq busy, discarding query") in
+ let f h k = f av h (fun () -> update_status h k) in
+ Coq.try_grab term.toplvl f info
module Nav = struct
- let forward_one _ = do_or_activate (fun h a -> a#process_next_phrase h true)
- let backward_one _ = do_or_activate (fun h a -> a#backtrack_last_phrase h)
- let goto _ = do_or_activate (fun h a -> a#go_to_insert h)
+ let forward_one _ = send_to_coq (fun a -> a#process_next_phrase)
+ let backward_one _ = send_to_coq (fun a -> a#backtrack_last_phrase)
+ let goto _ = send_to_coq (fun a -> a#go_to_insert)
let restart _ = force_reset_initial ()
- let goto_end _ = do_or_activate (fun h a -> a#process_until_end_or_error h)
+ let goto_end _ = send_to_coq (fun a -> a#process_until_end_or_error)
let interrupt _ = break ()
let previous_occ _ = (current_view ())#find_next_occurrence Up
let next_occ _ = (current_view ())#find_next_occurrence Down
end
-let tactic_wizard_callback l _ =
- do_if_active (fun h a -> a#tactic_wizard h l)
+let tactic_wizard_callback l _ = send_to_coq (fun a -> a#tactic_wizard l)
let printopts_callback opts v =
let b = v#get_active in
let opts = List.map (fun o -> (o,b)) opts in
- do_or_activate (fun h av ->
- Coq.PrintOpt.set h opts;
- av#show_goals h)
+ send_to_coq (fun av h k ->
+ Coq.PrintOpt.set opts h
+ (fun () -> av#show_goals h k))
(** Templates menu *)
@@ -1497,31 +1453,31 @@ let print_branches c cases =
Format.fprintf c "@[match var with@\n%aend@]@."
(print_list print_branch) cases
+let display_match k = function
+ |Interface.Fail _ -> flash_info "Not an inductive type"; k ()
+ |Interface.Good cases |Interface.Unsafe cases ->
+ let text =
+ let buf = Buffer.create 1024 in
+ let () = print_branches (Format.formatter_of_buffer buf) cases in
+ Buffer.contents buf
+ in
+ Minilib.log ("match template :\n" ^ text);
+ let b = current_buffer () in
+ let _ = b#delete_selection () in
+ let m = b#create_mark (b#get_iter_at_mark `INSERT) in
+ if b#insert_interactive text then begin
+ let i = b#get_iter (`MARK m) in
+ let _ = i#nocopy#forward_chars 9 in
+ let _ = b#place_cursor ~where:i in
+ b#move_mark ~where:(i#backward_chars 3) `SEL_BOUND
+ end;
+ b#delete_mark (`MARK m);
+ k ()
+
let match_callback _ =
let w = get_current_word () in
let coqtop = session_notebook#current_term.toplvl in
- try
- Coq.try_grab coqtop begin fun handle -> match Coq.mkcases handle w with
- | Interface.Fail _ -> raise Not_found
- | Interface.Good cases | Interface.Unsafe cases ->
- let text =
- let buf = Buffer.create 1024 in
- let () = print_branches (Format.formatter_of_buffer buf) cases in
- Buffer.contents buf
- in
- Minilib.log ("match template :\n" ^ text);
- let b = current_buffer () in
- let _ = b#delete_selection () in
- let m = b#create_mark (b#get_iter `INSERT) in
- if b#insert_interactive text then begin
- let i = b#get_iter (`MARK m) in
- let _ = i#nocopy#forward_chars 9 in
- let _ = b#place_cursor ~where:i in
- b#move_mark ~where:(i#backward_chars 3) `SEL_BOUND
- end;
- b#delete_mark (`MARK m)
- end ignore
- with Not_found -> flash_info "Not an inductive type"
+ Coq.try_grab coqtop (fun h k -> Coq.mkcases w h (display_match k)) ignore
(** Queries *)
@@ -1530,33 +1486,33 @@ module Query = struct
let searchabout () =
let word = get_current_word () in
let term = session_notebook#current_term in
- let query handle =
- let results = Coq.search handle [Interface.SubType_Pattern word, true] in
- let results = match results with | Interface.Good l -> l | _ -> [] in
- let buf = term.message_view#buffer in
- let insert result =
- let qualid = result.Interface.coq_object_qualid in
- let name = String.concat "." qualid in
- let tpe = result.Interface.coq_object_object in
- buf#insert ~tags:[Tags.Message.item] name;
- buf#insert "\n";
- buf#insert tpe;
- buf#insert "\n";
- in
+ let buf = term.message_view#buffer in
+ let insert result =
+ let qualid = result.Interface.coq_object_qualid in
+ let name = String.concat "." qualid in
+ let tpe = result.Interface.coq_object_object in
+ buf#insert ~tags:[Tags.Message.item] name;
+ buf#insert "\n";
+ buf#insert tpe;
+ buf#insert "\n";
+ in
+ let display_results k r =
term.message_view#clear ();
- List.iter insert results
+ List.iter insert (match r with Interface.Good l -> l | _ -> []);
+ k ()
in
- Coq.try_grab term.toplvl query ignore
+ let launch_query h k =
+ Coq.search [Interface.SubType_Pattern word, true] h (display_results k)
+ in
+ Coq.try_grab term.toplvl launch_query ignore
let otherquery command _ =
let word = get_current_word () in
let term = session_notebook#current_term in
- let f query handle = term.analyzed_view#raw_coq_query handle query in
- if not (word = "") then
+ if word <> "" then
let query = command ^ " " ^ word ^ "." in
term.message_view#clear ();
- try Coq.try_grab term.toplvl (f query) ignore
- with e -> term.message_view#push Interface.Error (Printexc.to_string e)
+ Coq.try_grab term.toplvl (term.analyzed_view#raw_coq_query query) ignore
let query command _ =
if command = "SearchAbout"
@@ -1831,13 +1787,9 @@ let build_ui () =
menu edit_menu [
item "Edit" ~label:"_Edit";
item "Undo" ~accel:"<Ctrl>u" ~stock:`UNDO
- ~callback:(fun _ ->
- let term = session_notebook#current_term in
- do_if_not_computing term "undo"
- (fun handle ->
- ignore (term.script#undo ())));
+ ~callback:(fun _ -> session_notebook#current_term.script#undo ());
item "Redo" ~stock:`REDO
- ~callback:(fun _ -> ignore (session_notebook#current_term.script#redo ()));
+ ~callback:(fun _ -> session_notebook#current_term.script#redo ());
item "Cut" ~stock:`CUT
~callback:(fun _ -> emit_to_focus GtkText.View.S.cut_clipboard);
item "Copy" ~stock:`COPY
@@ -2048,8 +2000,8 @@ let build_ui () =
let () = vbox#pack toolbar in
(* Reset on tab switch *)
- let _ = session_notebook#connect#switch_page
- (fun _ -> if prefs.reset_on_tab_switch then force_reset_initial ())
+ let _ = session_notebook#connect#switch_page ~callback:(fun _ ->
+ if prefs.reset_on_tab_switch then force_reset_initial ())
in
(* Vertical Separator between Scripts and Goals *)
@@ -2129,23 +2081,21 @@ let main files =
session_notebook#current_term.script#misc#grab_focus ();
Minilib.log "End of Coqide.main"
-(* This function check every half of second if GeoProof has send
+(* This function check every tenth of second if GeoProof has send
something on his private clipboard *)
-let rec check_for_geoproof_input () =
+let check_for_geoproof_input () =
let cb_Dr = GData.clipboard (Gdk.Atom.intern "_GeoProof") in
- while true do
- Thread.delay 0.1;
- let s = cb_Dr#text in
- (match s with
- Some s ->
- if s <> "Ack" then (current_buffer ())#insert (s^"\n");
- cb_Dr#set_text "Ack"
- | None -> ()
- );
- (* cb_Dr#clear does not work so i use : *)
- (* cb_Dr#set_text "Ack" *)
- done
+ let handler () = match cb_Dr#text with
+ |None -> true
+ |Some "Ack" -> true
+ |Some s ->
+ (current_buffer ())#insert (s^"\n");
+ (* cb_Dr#clear does not work so i use : *)
+ cb_Dr#set_text "Ack";
+ true
+ in
+ ignore (GMain.Timeout.add ~ms:100 ~callback:handler)
(** By default, the coqtop we try to launch is exactly the current coqide
full name, with the last occurrence of "coqide" replaced by "coqtop".
diff --git a/ide/coqide.mli b/ide/coqide.mli
index ca5396950..f9e711455 100644
--- a/ide/coqide.mli
+++ b/ide/coqide.mli
@@ -24,7 +24,11 @@ val main : string list -> unit
(** Function to save anything and kill all coqtops
@return [false] if you're allowed to quit. *)
-val forbid_quit_to_save : unit -> bool
+val forbid_quit : unit -> bool
+
+(** Terminate coqide after closing all coqtops and waiting
+ for their death *)
+val close_and_quit : unit -> unit
(** Function to load of a file. *)
val do_load : string -> unit
diff --git a/ide/coqide_main.ml4 b/ide/coqide_main.ml4
index 282dfce99..a9b10e60c 100644
--- a/ide/coqide_main.ml4
+++ b/ide/coqide_main.ml4
@@ -84,22 +84,13 @@ let reroute_stdout_stderr () =
(* We also provide specific kill and interrupt functions. *)
-(* Since [win32_interrupt] involves some hack about the process console,
- only one should run at the same time, we simply skip execution of
- [win32_interrupt] if another instance is already running *)
-
-let ctrl_c_mtx = Mutex.create ()
-
-let ctrl_c_protect f i =
- if not (Mutex.try_lock ctrl_c_mtx) then ()
- else try f i; Mutex.unlock ctrl_c_mtx with _ -> Mutex.unlock ctrl_c_mtx
-
IFDEF WIN32 THEN
external win32_kill : int -> unit = "win32_kill"
external win32_interrupt : int -> unit = "win32_interrupt"
let () =
Coq.killer := win32_kill;
- Coq.interrupter := ctrl_c_protect win32_interrupt;
+ Coq.soft_killer := win32_kill;
+ Coq.interrupter := win32_interrupt;
set_win32_path ();
reroute_stdout_stderr ()
END
@@ -115,6 +106,9 @@ let () =
in
let _ = osx#connect#ns_application_block_termination
~callback:Coqide.forbid_quit_to_save
+ in
+ let _ = osx#connect#ns_application_will_terminate
+ ~callback:Coqide.close_and_quit
in ()
let os_specific_init () =
@@ -147,10 +141,11 @@ let () =
Coq.check_connection args;
Coqide.sup_args := args;
Coqide.main files;
- if !Coq_config.with_geoproof then
- ignore (Thread.create Coqide.check_for_geoproof_input ());
+ if !Coq_config.with_geoproof then Coqide.check_for_geoproof_input ();
os_specific_init ();
- try GtkThread.main ()
+ try
+ GMain.main ();
+ failwith "Gtk loop ended"
with e ->
Minilib.log ("CoqIde unexpected error:" ^ Printexc.to_string e);
Coqide.crash_save 127
diff --git a/ide/ideutils.ml b/ide/ideutils.ml
index 9b7dcd67a..35a5a2477 100644
--- a/ide/ideutils.ml
+++ b/ide/ideutils.ml
@@ -15,9 +15,12 @@ exception Forbidden
let status = GMisc.statusbar ()
-let push_info,pop_info =
+let push_info,pop_info,clear_info =
let status_context = status#new_context ~name:"Messages" in
- (fun s -> ignore (status_context#push s)),status_context#pop
+ let size = ref 0 in
+ (fun s -> incr size; ignore (status_context#push s)),
+ (fun () -> decr size; status_context#pop ()),
+ (fun () -> for i = 1 to !size do status_context#pop () done; size := 0)
let flash_info =
let flash_context = status#new_context ~name:"Flash" in
@@ -219,35 +222,6 @@ let find_tag_stop (tag :GText.tag) (it:GText.iter) =
let find_tag_limits (tag :GText.tag) (it:GText.iter) =
(find_tag_start tag it , find_tag_stop tag it)
-(* explanations: Win32 threads won't work if events are produced
- in a thread different from the thread of the Gtk loop. In this
- case we must use GtkThread.async to push a callback in the
- main thread. Beware that the synchronus version may produce
- deadlocks. *)
-let async =
- if Sys.os_type = "Win32" then GtkThread.async else (fun x -> x)
-let sync =
- if Sys.os_type = "Win32" then GtkThread.sync else (fun x -> x)
-
-let mutex text f =
- let m = Mutex.create() in
- fun x ->
- if Mutex.try_lock m
- then
- (try
- Minilib.log ("Got lock on "^text);
- f x;
- Mutex.unlock m;
- Minilib.log ("Released lock on "^text)
- with e ->
- Mutex.unlock m;
- Minilib.log ("Released lock on "^text^" (on error)");
- raise e)
- else
- Minilib.log
- ("Discarded call for "^text^": computations ongoing")
-
-
let stock_to_widget ?(size=`DIALOG) s =
let img = GMisc.image ()
in img#set_stock s;
@@ -284,59 +258,6 @@ let rec print_list print fmt = function
let requote cmd = if Sys.os_type = "Win32" then "\""^cmd^"\"" else cmd
-let browse f url =
- let com = Util.subst_command_placeholder current.cmd_browse url in
- let _ = Unix.open_process_out com in ()
-(* This beautiful message will wait for twt ...
- if s = 127 then
- f ("Could not execute\n\""^com^
- "\"\ncheck your preferences for setting a valid browser command\n")
-*)
-let doc_url () =
- if current.doc_url = use_default_doc_url || current.doc_url = "" then
- let addr = List.fold_left Filename.concat (Coq_config.docdir)
- ["html";"refman";"index.html"]
- in
- if Sys.file_exists addr then "file://"^addr else Coq_config.wwwrefman
- else current.doc_url
-
-let url_for_keyword =
- let ht = Hashtbl.create 97 in
- lazy (
- begin try
- let cin =
- try let index_urls = Filename.concat (List.find
- (fun x -> Sys.file_exists (Filename.concat x "index_urls.txt"))
- (Minilib.coqide_config_dirs ())) "index_urls.txt" in
- open_in index_urls
- with Not_found ->
- let doc_url = doc_url () in
- let n = String.length doc_url in
- if n > 8 && String.sub doc_url 0 7 = "file://" then
- open_in (String.sub doc_url 7 (n-7) ^ "index_urls.txt")
- else
- raise Exit
- in
- try while true do
- let s = input_line cin in
- try
- let i = String.index s ',' in
- let k = String.sub s 0 i in
- let u = String.sub s (i + 1) (String.length s - i - 1) in
- Hashtbl.add ht k u
- with _ ->
- Minilib.log "Warning: Cannot parse documentation index file."
- done with End_of_file ->
- close_in cin
- with _ ->
- Minilib.log "Warning: Cannot find documentation index file."
- end;
- Hashtbl.find ht : string -> string)
-
-let browse_keyword f text =
- try let u = Lazy.force url_for_keyword text in browse f (doc_url() ^ u)
- with Not_found -> f ("No documentation found for \""^text^"\".\n")
-
let textview_width (view : #GText.view) =
let rect = view#visible_rect in
let pixel_width = Gdk.Rectangle.width rect in
@@ -344,6 +265,8 @@ let textview_width (view : #GText.view) =
let char_width = GPango.to_pixels metrics#approx_char_width in
pixel_width / char_width
+type logger = Interface.message_level -> string -> unit
+
let default_logger level message =
let level = match level with
| Interface.Debug _ -> `DEBUG
@@ -367,15 +290,119 @@ let stat f =
| Unix.Unix_error (Unix.ENOENT,_,_) -> NoSuchFile
| _ -> OtherError
+(** I/O utilities
+
+ Note: In a mono-thread coqide, we use the same buffer for
+ different read operations *)
+
+let maxread = 1024
+
+let read_string = String.create maxread
+let read_buffer = Buffer.create maxread
+
(** Read the content of file [f] and add it to buffer [b].
I/O Exceptions are propagated. *)
let read_file name buf =
let ic = open_in name in
- let s = String.create 1024 and len = ref 0 in
+ let len = ref 0 in
try
- while len := input ic s 0 1024; !len > 0 do
- Buffer.add_substring buf s 0 !len
+ while len := input ic read_string 0 maxread; !len > 0 do
+ Buffer.add_substring buf read_string 0 !len
done;
close_in ic
with e -> close_in ic; raise e
+
+(** Read a gtk asynchronous channel *)
+
+let io_read_all chan =
+ Buffer.clear read_buffer;
+ let rec loop () =
+ let len = Glib.Io.read ~buf:read_string ~pos:0 ~len:maxread chan in
+ Buffer.add_substring read_buffer read_string 0 len;
+ if len < maxread then Buffer.contents read_buffer
+ else loop ()
+ in loop ()
+
+(** Run an external command asynchronously *)
+
+let run_command display finally cmd =
+ let cin = Unix.open_process_in cmd in
+ let io_chan = Glib.Io.channel_of_descr (Unix.descr_of_in_channel cin) in
+ let all_conds = [`ERR; `HUP; `IN; `NVAL; `PRI] in (* all except `OUT *)
+ let rec has_errors = function
+ | [] -> false
+ | (`IN | `PRI) :: conds -> has_errors conds
+ | e :: _ -> true
+ in
+ let handle_end () = finally (Unix.close_process_in cin); false
+ in
+ let handle_input conds =
+ if has_errors conds then handle_end ()
+ else
+ let s = io_read_all io_chan in
+ if s = "" then handle_end ()
+ else (display (try_convert s); true)
+ in
+ ignore (Glib.Io.add_watch ~cond:all_conds ~callback:handle_input io_chan)
+
+(** Web browsing *)
+
+let browse prerr url =
+ let com = Util.subst_command_placeholder current.cmd_browse url in
+ let finally = function
+ | Unix.WEXITED 127 ->
+ prerr
+ ("Could not execute:\n"^com^"\n"^
+ "check your preferences for setting a valid browser command\n")
+ | _ -> ()
+ in
+ run_command (fun _ -> ()) finally com
+
+let doc_url () =
+ if current.doc_url = use_default_doc_url || current.doc_url = ""
+ then
+ let addr = List.fold_left Filename.concat (Coq_config.docdir)
+ ["html";"refman";"index.html"]
+ in
+ if Sys.file_exists addr then "file://"^addr else Coq_config.wwwrefman
+ else current.doc_url
+
+let url_for_keyword =
+ let ht = Hashtbl.create 97 in
+ lazy (
+ begin try
+ let cin =
+ try let index_urls = Filename.concat (List.find
+ (fun x -> Sys.file_exists (Filename.concat x "index_urls.txt"))
+ (Minilib.coqide_config_dirs ())) "index_urls.txt" in
+ open_in index_urls
+ with Not_found ->
+ let doc_url = doc_url () in
+ let n = String.length doc_url in
+ if n > 8 && String.sub doc_url 0 7 = "file://" then
+ open_in (String.sub doc_url 7 (n-7) ^ "index_urls.txt")
+ else
+ raise Exit
+ in
+ try while true do
+ let s = input_line cin in
+ try
+ let i = String.index s ',' in
+ let k = String.sub s 0 i in
+ let u = String.sub s (i + 1) (String.length s - i - 1) in
+ Hashtbl.add ht k u
+ with _ ->
+ Minilib.log "Warning: Cannot parse documentation index file."
+ done with End_of_file ->
+ close_in cin
+ with _ ->
+ Minilib.log "Warning: Cannot find documentation index file."
+ end;
+ Hashtbl.find ht : string -> string)
+
+let browse_keyword prerr text =
+ try
+ let u = Lazy.force url_for_keyword text in
+ browse prerr (doc_url() ^ u)
+ with Not_found -> prerr ("No documentation found for \""^text^"\".\n")
diff --git a/ide/ideutils.mli b/ide/ideutils.mli
index eb5cf1a56..2598dee33 100644
--- a/ide/ideutils.mli
+++ b/ide/ideutils.mli
@@ -6,12 +6,6 @@
(* * GNU Lesser General Public License Version 2.1 *)
(************************************************************************)
-val async : ('a -> unit) -> 'a -> unit
-val sync : ('a -> 'b) -> 'a -> 'b
-
-(* avoid running two instances of a function concurrently *)
-val mutex : string -> ('a -> unit) -> 'a -> unit
-
val doc_url : unit -> string
val browse : (string -> unit) -> string -> unit
val browse_keyword : (string -> unit) -> string -> unit
@@ -49,6 +43,7 @@ val coqtop_path : unit -> string
val status : GMisc.statusbar
val push_info : string -> unit
val pop_info : unit -> unit
+val clear_info : unit -> unit
val flash_info : ?delay:int -> string -> unit
val set_location : (string -> unit) ref
@@ -63,10 +58,12 @@ val requote : string -> string
val textview_width : #GText.view -> int
(** Returns an approximate value of the character width of a textview *)
+type logger = Interface.message_level -> string -> unit
+
val default_logger : Interface.message_level -> string -> unit
(** Default logger. It logs messages that the casual user should not see. *)
-(** {6 File operations} *)
+(** {6 I/O operations} *)
(** A customized [stat] function. Exceptions are catched. *)
@@ -77,3 +74,14 @@ val stat : string -> stats
I/O Exceptions are propagated. *)
val read_file : string -> Buffer.t -> unit
+
+(** Read the available content on some gtk asynchronous input channel *)
+
+val io_read_all : Glib.Io.channel -> string
+
+(** [run_command display finally cmd] allow to run a command
+ asynchronously, calling [display] on any output of this command
+ and [finally] when the command has returned. *)
+
+val run_command :
+ (string -> unit) -> (Unix.process_status -> unit) -> string -> unit
diff --git a/ide/wg_Command.ml b/ide/wg_Command.ml
index 2e4ce364d..98ce63faf 100644
--- a/ide/wg_Command.ml
+++ b/ide/wg_Command.ml
@@ -111,18 +111,16 @@ object(self)
if String.get com (String.length com - 1) = '.'
then com ^ " " else com ^ " " ^ entry#text ^" . "
in
- let insert level message =
- result#buffer#insert message;
- result#buffer#insert "\n"
+ let log level message = result#buffer#insert (message^"\n")
in
- let process handle =
- let answer = match Coq.interp handle insert ~raw:true phrase with
- | Interface.Fail (l,str) ->
- "Error while interpreting "^phrase^":\n"^str
- | Interface.Good results | Interface.Unsafe results ->
- "Result for command " ^ phrase ^ ":\n" ^ results
- in
- result#buffer#insert answer
+ let process h k =
+ Coq.interp ~logger:log ~raw:true phrase h (function
+ |Interface.Fail (l,str) ->
+ result#buffer#insert ("Error while interpreting "^phrase^":\n"^str);
+ k ()
+ |Interface.Good res | Interface.Unsafe res ->
+ result#buffer#insert ("Result for command " ^ phrase ^ ":\n" ^ res);
+ k ())
in
result#buffer#set_text "";
Coq.try_grab coqtop process ignore
@@ -130,17 +128,13 @@ object(self)
ignore (combo#entry#connect#activate ~callback:(on_activate callback));
ignore (ok_b#connect#clicked ~callback:(on_activate callback));
- begin match command,term with
- | None,None -> ()
- | Some c, None ->
- combo#entry#set_text c;
-
- | Some c, Some t ->
- combo#entry#set_text c;
- entry#set_text t
-
- | None , Some t ->
- entry#set_text t
+ begin match command with
+ | None -> ()
+ | Some c -> combo#entry#set_text c
+ end;
+ begin match term with
+ | None -> ()
+ | Some t -> entry#set_text t
end;
on_activate callback ();
entry#misc#grab_focus ();
diff --git a/ide/wg_ScriptView.ml b/ide/wg_ScriptView.ml
index 82dd77344..0ab946d65 100644
--- a/ide/wg_ScriptView.ml
+++ b/ide/wg_ScriptView.ml
@@ -42,7 +42,7 @@ end
module Proposals = Set.Make(StringOrd)
-let get_completion (buffer : GText.buffer) coqtop w =
+let get_completion (buffer : GText.buffer) coqtop w handle_res =
let rec get_aux accu (iter : GText.iter) =
match iter#forward_search w with
| None -> accu
@@ -56,23 +56,23 @@ let get_completion (buffer : GText.buffer) coqtop w =
else get_aux accu stop
in
let get_semantic accu =
- let ans = ref accu in
let flags = [Interface.Name_Pattern ("^" ^ w), true] in
- let query handle = match Coq.search handle flags with
- | Interface.Good l ->
- let fold accu elt =
- let rec last accu = function
- | [] -> accu
- | [basename] -> Proposals.add basename accu
- | _ :: l -> last accu l
- in
- last accu elt.Interface.coq_object_qualid
- in
- ans := (List.fold_left fold accu l)
- | _ -> ()
+ let query h k =
+ Coq.search flags h
+ (function
+ | Interface.Good l ->
+ let fold accu elt =
+ let rec last accu = function
+ | [] -> accu
+ | [basename] -> Proposals.add basename accu
+ | _ :: l -> last accu l
+ in
+ last accu elt.Interface.coq_object_qualid
+ in
+ handle_res (List.fold_left fold accu l) k
+ | _ -> handle_res accu k)
in
Coq.try_grab coqtop query ignore;
- !ans
in
get_semantic (get_aux Proposals.empty buffer#start_iter)
@@ -88,8 +88,6 @@ object (self)
(* this variable prevents CoqIDE from autocompleting when we have deleted something *)
val mutable is_auto_completing = false
- val undo_lock = Mutex.create ()
-
method auto_complete = auto_complete
method set_auto_complete flag =
@@ -142,51 +140,35 @@ object (self)
self#buffer#insert_interactive ~iter s
method undo () =
- if Mutex.try_lock undo_lock then begin
- Minilib.log "UNDO";
- let effective = self#process_action history in
- if effective then self#backward ();
- Mutex.unlock undo_lock;
- effective
- end else
- (Minilib.log "UNDO DISCARDED"; true)
+ Minilib.log "UNDO";
+ let effective = self#process_action history in
+ if effective then self#backward ()
method redo () =
- if Mutex.try_lock undo_lock then begin
- Minilib.log "REDO";
- let effective = self#process_action redo in
- if effective then self#forward ();
- Mutex.unlock undo_lock;
- effective
- end else
- (Minilib.log "REDO DISCARDED"; true)
+ Minilib.log "REDO";
+ let effective = self#process_action redo in
+ if effective then self#forward ()
method private handle_insert iter s =
(* we're inserting, so we may autocomplete *)
is_auto_completing <- true;
(* Save the insert action *)
- if Mutex.try_lock undo_lock then begin
- let action = Insert (s, iter#offset, Glib.Utf8.length s) in
- history <- action :: history;
- redo <- [];
- self#dump_debug ();
- Mutex.unlock undo_lock
- end
+ let action = Insert (s, iter#offset, Glib.Utf8.length s) in
+ history <- action :: history;
+ redo <- [];
+ self#dump_debug ()
method private handle_delete ~start ~stop =
(* disable autocomplete *)
is_auto_completing <- false;
(* Save the delete action *)
- if Mutex.try_lock undo_lock then begin
- let start_offset = start#offset in
- let stop_offset = stop#offset in
- let s = self#buffer#get_text ~start ~stop () in
- let action = Delete (s, start_offset, stop_offset - start_offset) in
- history <- action :: history;
- redo <- [];
- self#dump_debug ();
- Mutex.unlock undo_lock
- end
+ let start_offset = start#offset in
+ let stop_offset = stop#offset in
+ let s = self#buffer#get_text ~start ~stop () in
+ let action = Delete (s, start_offset, stop_offset - start_offset) in
+ history <- action :: history;
+ redo <- [];
+ self#dump_debug ();
method private do_auto_complete () =
let iter = self#buffer#get_iter `INSERT in
@@ -197,33 +179,35 @@ object (self)
if String.length w >= auto_complete_length then begin
Minilib.log ("Completion of prefix: '" ^ w ^ "'");
let (off, prefix, proposals) = last_completion in
- let new_proposals =
- (* check whether we have the last request in cache *)
- if (start#offset = off) && (0 <= is_substring prefix w) then
- Proposals.filter (fun p -> 0 < is_substring w p) proposals
- else
- let ans = get_completion self#buffer ct w in
- let () = last_completion <- (start#offset, w, ans) in
- ans
- in
- let prop =
- try Some (Proposals.choose new_proposals)
- with Not_found -> None
- in
- match prop with
- | None -> ()
- | Some proposal ->
- let suffix =
- let len1 = String.length proposal in
- let len2 = String.length w in
- String.sub proposal len2 (len1 - len2)
+ let handle_proposals isnew new_proposals k =
+ if isnew then last_completion <- (start#offset, w, new_proposals);
+ let prop =
+ try Some (Proposals.choose new_proposals)
+ with Not_found -> None
in
- let offset = iter#offset in
- ignore (self#buffer#delete_selection ());
- ignore (self#buffer#insert_interactive suffix);
- let ins = self#buffer#get_iter (`OFFSET offset) in
- let sel = self#buffer#get_iter `INSERT in
- self#buffer#select_range sel ins
+ match prop with
+ | None -> k ()
+ | Some proposal ->
+ let suffix =
+ let len1 = String.length proposal in
+ let len2 = String.length w in
+ String.sub proposal len2 (len1 - len2)
+ in
+ let offset = iter#offset in
+ ignore (self#buffer#delete_selection ());
+ ignore (self#buffer#insert_interactive suffix);
+ let ins = self#buffer#get_iter (`OFFSET offset) in
+ let sel = self#buffer#get_iter `INSERT in
+ self#buffer#select_range sel ins;
+ k ()
+ in
+ (* check whether we have the last request in cache *)
+ if (start#offset = off) && (0 <= is_substring prefix w) then
+ handle_proposals false
+ (Proposals.filter (fun p -> 0 < is_substring w p) proposals)
+ (fun () -> ())
+ else
+ get_completion self#buffer ct w (handle_proposals true)
end
end
@@ -340,8 +324,10 @@ object (self)
(* Install auto-completion *)
ignore (self#buffer#connect#after#end_user_action ~callback:self#may_auto_complete);
(* HACK: Redirect the undo/redo signals of the underlying GtkSourceView *)
- ignore (self#connect#undo (fun _ -> ignore (self#undo ()); GtkSignal.stop_emit()));
- ignore (self#connect#redo (fun _ -> ignore (self#redo ()); GtkSignal.stop_emit()));
+ ignore (self#connect#undo
+ ~callback:(fun _ -> ignore (self#undo ()); GtkSignal.stop_emit()));
+ ignore (self#connect#redo
+ ~callback:(fun _ -> ignore (self#redo ()); GtkSignal.stop_emit()));
(* HACK: Redirect the move_line signal of the underlying GtkSourceView *)
let move_line_marshal = GtkSignal.marshal2
Gobject.Data.boolean Gobject.Data.int "move_line_marshal"
@@ -360,9 +346,11 @@ object (self)
(* do we forward the signal? *)
let proceed =
if not b && i = 1 then
- iter#editable true && iter#forward_line#editable true
+ iter#editable ~default:true &&
+ iter#forward_line#editable ~default:true
else if not b && i = -1 then
- iter#editable true && iter#backward_line#editable true
+ iter#editable ~default:true &&
+ iter#backward_line#editable ~default:true
else false
in
if not proceed then GtkSignal.stop_emit ()
@@ -376,11 +364,13 @@ let script_view ct ?(source_buffer:GSourceView2.source_buffer option) ?draw_spa
GtkSourceView2.SourceView.make_params [] ~cont:(
GtkText.View.make_params ~cont:(
GContainer.pack_container ~create:
- (fun pl -> let w = match source_buffer with
- | None -> GtkSourceView2.SourceView.new_ ()
- | Some buf -> GtkSourceView2.SourceView.new_with_buffer
- (Gobject.try_cast buf#as_buffer "GtkSourceBuffer") in
- let w = Gobject.unsafe_cast w in
- Gobject.set_params (Gobject.try_cast w "GtkSourceView") pl;
- Gaux.may (GtkSourceView2.SourceView.set_draw_spaces w) draw_spaces;
- ((new script_view w ct) : script_view))))
+ (fun pl ->
+ let w = match source_buffer with
+ | None -> GtkSourceView2.SourceView.new_ ()
+ | Some buf -> GtkSourceView2.SourceView.new_with_buffer
+ (Gobject.try_cast buf#as_buffer "GtkSourceBuffer")
+ in
+ let w = Gobject.unsafe_cast w in
+ Gobject.set_params (Gobject.try_cast w "GtkSourceView") pl;
+ Gaux.may ~f:(GtkSourceView2.SourceView.set_draw_spaces w) draw_spaces;
+ ((new script_view w ct) : script_view))))
diff --git a/ide/wg_ScriptView.mli b/ide/wg_ScriptView.mli
index e94ce4904..efe33f54d 100644
--- a/ide/wg_ScriptView.mli
+++ b/ide/wg_ScriptView.mli
@@ -13,8 +13,8 @@ type source_view = [ Gtk.text_view | `sourceview ] Gtk.obj
class script_view : source_view -> Coq.coqtop ->
object
inherit GSourceView2.source_view
- method undo : unit -> bool
- method redo : unit -> bool
+ method undo : unit -> unit
+ method redo : unit -> unit
method clear_undo : unit -> unit
method auto_complete : bool
method set_auto_complete : bool -> unit
diff --git a/myocamlbuild.ml b/myocamlbuild.ml
index 793522f84..b08e54673 100644
--- a/myocamlbuild.ml
+++ b/myocamlbuild.ml
@@ -308,9 +308,9 @@ let extra_rules () = begin
flag ["ocaml"; "ide"; "compile"] lablgtkincl;
flag ["ocaml"; "ide"; "link"] lablgtkincl;
flag ["ocaml"; "ide"; "link"; "byte"]
- (S [A"lablgtk.cma"; A"lablgtksourceview2.cma"; A"gtkThread.cmo"]);
+ (S [A"lablgtk.cma"; A"lablgtksourceview2.cma"]);
flag ["ocaml"; "ide"; "link"; "native"]
- (S [A"lablgtk.cmxa"; A"lablgtksourceview2.cmxa"; A"gtkThread.cmx"]);
+ (S [A"lablgtk.cmxa"; A"lablgtksourceview2.cmxa"]);
(** C code for the VM *)