aboutsummaryrefslogtreecommitdiffhomepage
path: root/ide/wg_ScriptView.ml
diff options
context:
space:
mode:
authorGravatar ppedrot <ppedrot@85f007b7-540e-0410-9357-904b9bb8a0f7>2013-01-25 21:30:47 +0000
committerGravatar ppedrot <ppedrot@85f007b7-540e-0410-9357-904b9bb8a0f7>2013-01-25 21:30:47 +0000
commit5461bb1e86395883baf77222199732f667876a7a (patch)
tree22f9b7c615e1319565720a0f923cb591aa24c521 /ide/wg_ScriptView.ml
parentb011869c7c9d18aad8ccd4d1ced0b6c36e472863 (diff)
Trying to fix CoqIDE undo/redo mechanism
git-svn-id: svn+ssh://scm.gforge.inria.fr/svn/coq/trunk@16146 85f007b7-540e-0410-9357-904b9bb8a0f7
Diffstat (limited to 'ide/wg_ScriptView.ml')
-rw-r--r--ide/wg_ScriptView.ml229
1 files changed, 165 insertions, 64 deletions
diff --git a/ide/wg_ScriptView.ml b/ide/wg_ScriptView.ml
index 4484dc4d8..f8a442b17 100644
--- a/ide/wg_ScriptView.ml
+++ b/ide/wg_ScriptView.ml
@@ -25,8 +25,10 @@ type delete_action = {
type action =
| Insert of insert_action
| Delete of delete_action
+ | Action of action list
+ | EndGrp (** pending begin_user_action *)
-let neg act = match act with
+let rec negate_action act = match act with
| Insert act ->
let act = {
del_len = act.ins_len;
@@ -41,6 +43,9 @@ let neg act = match act with
ins_val = act.del_val;
} in
Insert act
+ | Action acts ->
+ Action (List.rev_map negate_action acts)
+ | EndGrp -> assert false
type source_view = [ Gtk.text_view | `sourceview ] Gtk.obj
@@ -100,92 +105,144 @@ let get_completion (buffer : GText.buffer) coqtop w handle_res =
in
get_semantic (get_aux Proposals.empty buffer#start_iter)
-class script_view (tv : source_view) (ct : Coq.coqtop) =
-object (self)
- inherit GSourceView2.source_view (Gobject.unsafe_cast tv) as super
-
+class undo_manager (buffer : GText.buffer) =
+object(self)
+ val mutable lock_undo = true
val mutable history = []
val mutable redo = []
- val mutable auto_complete = false
- val mutable auto_complete_length = 3
- val mutable last_completion = (-1, "", Proposals.empty)
- (* this variable prevents CoqIDE from autocompleting when we have deleted something *)
- val mutable is_auto_completing = false
- (* this mutex ensure that CoqIDE will not try to autocomplete twice *)
- val mutable lock_auto_completing = true
-
- method auto_complete = auto_complete
- method set_auto_complete flag =
- auto_complete <- flag
+ method with_lock_undo : 'a. ('a -> unit) -> 'a -> unit =
+ fun f x ->
+ if lock_undo then
+ let () = lock_undo <- false in
+ try (f x; lock_undo <- true)
+ with e -> (lock_undo <- true; raise e)
+ else ()
method private dump_debug () =
- let iter = function
+ let rec iter = function
| Insert act ->
- Printf.eprintf "Insert of '%s' at %d (length %d)\n"
+ Printf.eprintf "Insert of '%s' at %d (length %d)\n%!"
act.ins_val act.ins_off act.ins_len
| Delete act ->
- Printf.eprintf "Delete '%s' from %d (length %d)\n"
+ Printf.eprintf "Delete '%s' from %d (length %d)\n%!"
act.del_val act.del_off act.del_len
+ | Action l ->
+ Printf.eprintf "Action\n%!";
+ List.iter iter l;
+ Printf.eprintf "//Action\n%!";
+ | EndGrp ->
+ Printf.eprintf "End Group\n%!"
in
if false (* !debug *) then begin
- Minilib.log "==========Undo Stack top=============";
+ Printf.eprintf "==========Undo Stack top=============\n%!";
List.iter iter history;
Printf.eprintf "Stack size %d\n" (List.length history);
- Minilib.log "==========Undo Stack Bottom==========";
- Minilib.log "==========Redo Stack start=============";
+ Printf.eprintf "==========Undo Stack Bottom==========\n%!";
+ Printf.eprintf "==========Redo Stack start=============\n%!";
List.iter iter redo;
Printf.eprintf "Stack size %d\n" (List.length redo);
- Minilib.log "==========Redo Stack End=========="
+ Printf.eprintf "==========Redo Stack End==========\n%!"
end
- method recenter_insert =
- self#scroll_to_mark
- ~use_align:false ~yalign:0.75 ~within_margin:0.25 `INSERT
-
method clear_undo () =
history <- [];
redo <- []
- method private forward () =
- match redo with
- | [] -> ()
- | action :: h ->
- redo <- h;
- history <- neg action :: history
+ (** Warning: processing actually undo the action *)
+ method private process_insert_action ins =
+ let start = buffer#get_iter (`OFFSET ins.ins_off) in
+ let stop = start#forward_chars ins.ins_len in
+ buffer#delete_interactive ~start ~stop ()
- method private backward () =
- match history with
- | [] -> ()
- | action :: h ->
- history <- h;
- redo <- neg action :: redo
+ method private process_delete_action del =
+ let iter = buffer#get_iter (`OFFSET del.del_off) in
+ buffer#insert_interactive ~iter del.del_val
+ (** We don't care about atomicity. Return:
+ 1. `OK when there was no error, `FAIL otherwise
+ 2. `NOOP if no write occured, `WRITE otherwise
+ *)
method private process_action = function
- | [] -> false
- | Insert ins :: history ->
- let start = self#buffer#get_iter (`OFFSET ins.ins_off) in
- let stop = start#forward_chars ins.ins_len in
- self#buffer#delete_interactive ~start ~stop ()
- | Delete del :: history ->
- let iter = self#buffer#get_iter (`OFFSET del.del_off) in
- self#buffer#insert_interactive ~iter del.del_val
+ | Insert ins ->
+ if self#process_insert_action ins then (`OK, `WRITE) else (`FAIL, `NOOP)
+ | Delete del ->
+ if self#process_delete_action del then (`OK, `WRITE) else (`FAIL, `NOOP)
+ | Action lst ->
+ let fold accu action = match accu with
+ | (`FAIL, _) -> accu (** we stop now! *)
+ | (`OK, status) ->
+ let (res, nstatus) = self#process_action action in
+ let merge op1 op2 = match op1, op2 with
+ | `NOOP, `NOOP -> `NOOP (** only a noop when both are *)
+ | _ -> `WRITE
+ in
+ (res, merge status nstatus)
+ in
+ List.fold_left fold (`OK, `NOOP) lst
+ | EndGrp -> assert false
+
+ method perform_undo () = match history with
+ | [] -> ()
+ | action :: rem ->
+ let ans = self#process_action action in
+ begin match ans with
+ | (`OK, _) ->
+ history <- rem;
+ redo <- (negate_action action) :: redo
+ | (`FAIL, `NOOP) -> () (** we do nothing *)
+ | (`FAIL, `WRITE) -> self#clear_undo () (** we don't know how we failed, so start off *)
+ end
+
+ method perform_redo () = match redo with
+ | [] -> ()
+ | action :: rem ->
+ let ans = self#process_action action in
+ begin match ans with
+ | (`OK, _) ->
+ redo <- rem;
+ history <- (negate_action action) :: history;
+ | (`FAIL, `NOOP) -> () (** we do nothing *)
+ | (`FAIL, `WRITE) -> self#clear_undo () (** we don't know how we failed *)
+ end
method undo () =
Minilib.log "UNDO";
- let effective = self#process_action history in
- if effective then self#backward ()
+ self#with_lock_undo self#perform_undo ();
method redo () =
Minilib.log "REDO";
- let effective = self#process_action redo in
- if effective then self#forward ()
+ self#with_lock_undo self#perform_redo ();
+
+ method process_begin_user_action () =
+ (* Push a new level of event on history stack *)
+ history <- EndGrp :: history
+
+ method begin_user_action () =
+ self#with_lock_undo self#process_begin_user_action ()
+
+ method process_end_user_action () =
+ (** Search for the pending action *)
+ let rec split accu = function
+ | [] -> raise Not_found (** no pending begin action! *)
+ | EndGrp :: rem ->
+ let grp = List.rev accu in
+ begin match grp with
+ | [] -> rem
+ | _ -> Action grp :: rem
+ end
+ | action :: rem ->
+ split (action :: accu) rem
+ in
+ try history <- split [] history
+ with Not_found ->
+ Minilib.log "Error: Badly parenthezised user action";
+ self#clear_undo ()
- method private handle_insert iter s =
- (* we're inserting, so we may autocomplete *)
- is_auto_completing <- true
+ method end_user_action () =
+ self#with_lock_undo self#process_end_user_action ()
- method private handle_after_insert iter s =
+ method private process_handle_insert iter s =
(* Save the insert action *)
let ins = {
ins_val = s;
@@ -195,23 +252,65 @@ object (self)
let action = Insert ins in
history <- action :: history;
redo <- [];
- self#dump_debug ()
- method private handle_delete ~start ~stop =
- (* disable autocomplete *)
- is_auto_completing <- false
+ method private handle_insert iter s =
+ self#with_lock_undo (self#process_handle_insert iter) s
- method private handle_after_delete ~start ~stop =
+ method private process_handle_delete start stop =
(* Save the delete action *)
let del = {
- del_val = self#buffer#get_text ~start ~stop ();
+ del_val = buffer#get_text ~start ~stop ();
del_off = start#offset;
del_len = stop#offset - start#offset;
} in
let action = Delete del in
history <- action :: history;
redo <- [];
- self#dump_debug ();
+
+ method private handle_delete ~start ~stop =
+ self#with_lock_undo (self#process_handle_delete start) stop
+
+ initializer
+ let _ = buffer#connect#after#begin_user_action ~callback:self#begin_user_action in
+ let _ = buffer#connect#after#end_user_action ~callback:self#end_user_action in
+ let _ = buffer#connect#insert_text ~callback:self#handle_insert in
+ let _ = buffer#connect#delete_range ~callback:self#handle_delete in
+ ()
+
+end
+
+class script_view (tv : source_view) (ct : Coq.coqtop) =
+
+let _obj_ = new GSourceView2.source_view (Gobject.unsafe_cast tv) in
+
+object (self)
+ inherit GSourceView2.source_view (Gobject.unsafe_cast tv) as super
+
+ val mutable auto_complete = false
+ val mutable auto_complete_length = 3
+ val mutable last_completion = (-1, "", Proposals.empty)
+ (* this variable prevents CoqIDE from autocompleting when we have deleted something *)
+ val mutable is_auto_completing = false
+ (* this mutex ensure that CoqIDE will not try to autocomplete twice *)
+ val mutable lock_auto_completing = true
+ val undo_manager = new undo_manager _obj_#buffer
+
+ method auto_complete = auto_complete
+
+ method set_auto_complete flag =
+ auto_complete <- flag
+
+ method recenter_insert =
+ self#scroll_to_mark
+ ~use_align:false ~yalign:0.75 ~within_margin:0.25 `INSERT
+
+ method private handle_insert iter s =
+ (* we're inserting, so we may autocomplete *)
+ is_auto_completing <- true
+
+ method private handle_delete ~start ~stop =
+ (* disable autocomplete *)
+ is_auto_completing <- false
method private do_auto_complete () =
let iter = self#buffer#get_iter `INSERT in
@@ -368,10 +467,12 @@ object (self)
self#buffer#delete_mark (`MARK stop_mark)
| _ -> ()
+ method undo = undo_manager#undo
+ method redo = undo_manager#redo
+ method clear_undo = undo_manager#clear_undo
+
initializer
(* Install undo managing *)
- ignore (self#buffer#connect#after#insert_text ~callback:self#handle_after_insert);
- ignore (self#buffer#connect#after#delete_range ~callback:self#handle_after_delete);
(* Install auto-completion *)
ignore (self#buffer#connect#insert_text ~callback:self#handle_insert);
ignore (self#buffer#connect#delete_range ~callback:self#handle_delete);