let query_replace_gen
  ?(mes="")
  command_name
  (fsearch_buffer : search_buffer_function)
  (freplace : searched: string -> found:string -> repl:string -> string)
  (v : sourceview) args =
  let mb = v#minibuffer in
  let len = Array.length args in
  if len <= 0 then
    let f s = Cam_commands.launch_command command_name [| s |] in
    let title = Printf.sprintf "Query-replace%s" mes in
    Ed_misc.input_string ~history: replace_history
      mb ~title "" f
  else
    if len = 1 then
      let title = Ed_misc.to_utf8
        (Printf.sprintf "Query-replace%s %s with" mes args.(0))
      in
      let f s = Cam_commands.launch_command command_name [| args.(0); s |] in
      Ed_misc.input_string ~history: replace_history
        mb ~title "" f
    else
      let title = Ed_misc.to_utf8
        (Printf.sprintf "Query-replace%s %s with %s (y/n/!)"
         mes args.(0) args.(1))
      in
      let s1_utf8 = Ed_misc.to_utf8 args.(0) in
      let s2_utf8 = Ed_misc.to_utf8 args.(1) in
      let rec iter interactive =
        let b = v#file#buffer in
        let it = b#get_iter `INSERT in
        let start = it in
        match fsearch_buffer true (*=forward*) b ~start s1_utf8 with
          true, _
        | _, None -> mb#set_active false
        | falseSome (start,stop) ->
            if interactive then
              (
               v#set_location (location_of_iter start);
               b#select_range start stop;
               ignore(v#source_view#scroll_to_iter start);
               ignore(v#source_view#scroll_to_iter stop)
              );
            let replace () =
              v#place_cursor ~scroll: interactive start;
              let found = b#get_text ~start ~stop () in
              b#delete ~start ~stop;
(*              prerr_endline
                (Printf.sprintf "searched=%s, found=%s, repl=%s"
                 s1_utf8 found s2_utf8);
*)

              let new_text = freplace
                ~searched: s1_utf8 ~found ~repl: s2_utf8
              in
              b#insert new_text
            in
            if interactive then
              (
               let f_yes () = replace (); iter true in
               let f_no () =
                 v#place_cursor stop;
                 iter true
               in
               let f_bang () = replace (); iter false in
               mb#clear;
               mb#set_more_key_bindings
                 [ [[], GdkKeysyms._y], f_yes ;
                   [[], GdkKeysyms._n], f_no ;
                   [[], GdkKeysyms._exclam], f_bang ;
                 ];
               mb#set_text ~fixed: title "";
               if not mb#active then (mb#set_active true; mb#wait);
              )
            else
              (replace (); iter interactive)
      in
      iter true