--- /dev/null
+(* $Id$ *)
+
+open GdkKeysyms
+open Printf
+
+(* Nice history class. May reuse *)
+
+class ['a] history () = object
+ val mutable history = ([] : 'a list)
+ val mutable count = 0
+ method empty = history = []
+ method add s = count <- 0; history <- s :: history
+ method previous =
+ let s = List.nth history count in
+ count <- (count + 1) mod List.length history;
+ s
+ method next =
+ let l = List.length history in
+ count <- (l + count - 1) mod l;
+ List.nth history ((l + count - 1) mod l)
+end
+
+(* The shell class. Now encapsulated *)
+
+let protect f x = try f x with _ -> ()
+
+class shell ~prog ~args ~env ?packing ?show () =
+ let (in2,out1) = Unix.pipe ()
+ and (in1,out2) = Unix.pipe ()
+ and (err1,err2) = Unix.pipe () in
+ let _ = List.iter ~f:Unix.set_nonblock [out1;in1;err1] in
+object (self)
+ val textw = GEdit.text ~editable:true ?packing ?show ()
+ val pid = Unix.create_process_env
+ ~prog ~args ~env ~stdin:in2 ~stdout:out2 ~stderr:err2
+ val out = Unix.out_channel_of_descr out1
+ val h = new history ()
+ val mutable alive = true
+ val mutable reading = false
+ val mutable input_start = 0
+ method text = textw
+ method alive = alive
+ method kill () =
+ textw#set_editable false;
+ if alive then begin
+ alive <- false;
+ protect close_out out;
+ List.iter ~f:(protect Unix.close) [in1; err1; in2; out2; err2];
+ try
+ Unix.kill ~pid ~signal:Sys.sigkill;
+ Unix.waitpid pid ~mode:[]; ()
+ with _ -> ()
+ end
+ method interrupt () =
+ if alive then try
+ reading <- false;
+ Unix.kill ~pid ~signal:Sys.sigint
+ with Unix.Unix_error _ -> ()
+ method send s =
+ if alive then try
+ output_string out s;
+ flush out
+ with Sys_error _ -> ()
+ method private read ~fd ~len =
+ try
+ let buf = String.create len in
+ let len = Unix.read fd ~buf ~pos:0 ~len in
+ if len > 0 then begin
+ textw#set_position textw#length;
+ self#insert (String.sub buf ~pos:0 ~len);
+ input_start <- textw#position;
+ end;
+ len
+ with Unix.Unix_error _ -> 0
+ method history (dir : [`next|`previous]) =
+ if not h#empty then begin
+ if reading then begin
+ textw#delete_text ~start:input_start ~stop:textw#position;
+ end else begin
+ reading <- true;
+ input_start <- textw#position
+ end;
+ self#insert (if dir = `previous then h#previous else h#next);
+ end
+ val mutable lexing = false
+ method private lex ~start ~stop:e =
+ if not lexing && start < e then begin
+ lexing <- true;
+ Lexical.tag textw ~start ~stop:e;
+ lexing <- false
+ end
+ method insert ?(lex=true) text =
+ let start = Text.line_start textw in
+ textw#insert text;
+ if lex then self#lex ~start ~stop:(Text.line_end textw)
+ method private keypress c =
+ if not reading & c > " " then begin
+ reading <- true;
+ input_start <- textw#position
+ end
+ method private return () =
+ if reading then reading <- false
+ else input_start <- textw#position;
+ textw#set_position (Text.line_end textw);
+ let s = textw#get_chars ~start:input_start ~stop:textw#position in
+ h#add s;
+ self#send s;
+ self#send "\n"
+ method private paste () =
+ if not reading then begin
+ reading <- true;
+ input_start <- textw#position;
+ end
+ initializer
+ textw#event#connect#key_press ~callback:
+ begin fun ev ->
+ if GdkEvent.Key.keyval ev = _Return && GdkEvent.Key.state ev = []
+ then self#return ()
+ else self#keypress (GdkEvent.Key.string ev);
+ false
+ end;
+ textw#connect#after#insert_text ~callback:
+ begin fun s ~pos ->
+ if not lexing then
+ self#lex ~start:(Text.line_start textw ~pos:(pos - String.length s))
+ ~stop:(Text.line_end textw ~pos)
+ end;
+ textw#connect#after#delete_text ~callback:
+ begin fun ~start:pos ~stop ->
+ if not lexing then
+ self#lex ~start:(Text.line_start textw ~pos)
+ ~stop:(Text.line_end textw ~pos)
+ end;
+ textw#event#connect#button_press ~callback:
+ begin fun ev ->
+ if GdkEvent.Button.button ev = 2 then self#paste ();
+ false
+ end;
+ textw#connect#destroy ~callback:self#kill;
+ GMain.Timeout.add ~ms:100 ~callback:
+ begin fun () ->
+ if alive then begin
+ List.iter [err1;in1]
+ ~f:(fun fd -> while self#read ~fd ~len:1024 = 1024 do () done);
+ true
+ end else false
+ end;
+ ()
+end
+
+(* Specific use of shell, for LablBrowser *)
+
+let shells : (string * shell) list ref = ref []
+
+(* Called before exiting *)
+let kill_all () =
+ List.iter !shells ~f:(fun (_,sh) -> if sh#alive then sh#kill ());
+ shells := []
+let _ = at_exit kill_all
+
+let get_all () =
+ let all = List.filter !shells ~f:(fun (_,sh) -> sh#alive) in
+ shells := all;
+ all
+
+let may_exec prog =
+ try Unix.access prog ~perm:[Unix.X_OK]; true
+ with Unix.Unix_error _ -> false
+
+let f ~prog ~title =
+ let progargs =
+ List.filter ~f:((<>) "") (Str.split ~sep:(Str.regexp " ") prog) in
+ if progargs = [] then () else
+ let prog = List.hd progargs in
+ let path = try Sys.getenv "PATH" with Not_found -> "/bin:/usr/bin" in
+ let exec_path = Str.split ~sep:(Str.regexp":") path in
+ let prog =
+ if not (Filename.is_implicit prog) then
+ if may_exec prog then prog else ""
+ else
+ List.fold_left exec_path ~init:"" ~f:
+ begin fun acc dir ->
+ if acc <> "" then acc else
+ let prog = Filename.concat dir prog in
+ if may_exec prog then prog else acc
+ end
+ in
+ if prog = "" then () else
+ let reg = Str.regexp "TERM=" in
+ let env = Array.map (Unix.environment ()) ~f:
+ begin fun s ->
+ if Str.string_match ~pat:reg s ~pos:0 then "TERM=dumb" else s
+ end in
+ let load_path =
+ List.flatten (List.map !Config.load_path ~f:(fun dir -> ["-I"; dir])) in
+ let args = Array.of_list (progargs @ load_path) in
+ let current_dir = ref (Unix.getcwd ()) in
+
+ let tl = GWindow.window ~title ~width:500 ~height:300 () in
+ let vbox = GPack.vbox ~packing:tl#add () in
+ let menus = GMenu.menu_bar ~packing:vbox#pack () in
+ let f = new GMenu.factory menus in
+ let accel_group = f#accel_group in
+ let file_menu = f#add_submenu "File"
+ and history_menu = f#add_submenu "History"
+ and signal_menu = f#add_submenu "Signal" in
+
+ let hbox = GPack.hbox ~packing:vbox#add () in
+ let sh = new shell ~prog ~env ~args ~packing:hbox#add () in
+ let sb =
+ GRange.scrollbar `VERTICAL ~adjustment:sh#text#vadjustment
+ ~packing:hbox#pack ()
+ in
+
+ let f = new GMenu.factory file_menu ~accel_group in
+ f#add_item "Use..." ~callback:
+ begin fun () ->
+ File.dialog ~title:"Use File" ~filename:(!current_dir ^ "/") () ~callback:
+ begin fun name ->
+ current_dir := Filename.dirname name;
+ if Filename.check_suffix name ".ml" then
+ let cmd = "#use \"" ^ name ^ "\";;\n" in
+ sh#insert cmd;
+ sh#send cmd
+ end
+ end;
+ f#add_item "Load..." ~callback:
+ begin fun () ->
+ File.dialog ~title:"Load File" ~filename:(!current_dir ^ "/") () ~callback:
+ begin fun name ->
+ current_dir := Filename.dirname name;
+ if Filename.check_suffix name ".cmo" or
+ Filename.check_suffix name ".cma"
+ then
+ let cmd = Printf.sprintf "#load \"%s\";;\n" name in
+ sh#insert cmd;
+ sh#send cmd
+ end
+ end;
+ f#add_item "Import path" ~callback:
+ begin fun () ->
+ List.iter (List.rev !Config.load_path)
+ ~f:(fun dir -> sh#send (sprintf "#directory \"%s\";;\n" dir))
+ end;
+ f#add_item "Close" ~key:_W ~callback:tl#destroy;
+
+ let h = new GMenu.factory history_menu ~accel_group ~accel_modi:[`MOD1] in
+ h#add_item "Previous" ~key:_P ~callback:(fun () -> sh#history `previous);
+ h#add_item "Next" ~key:_N ~callback:(fun () -> sh#history `next);
+ let s = new GMenu.factory signal_menu ~accel_group in
+ s#add_item "Interrupt" ~key:_G ~callback:sh#interrupt;
+ s#add_item "Kill" ~callback:sh#kill;
+ shells := (title, sh) :: !shells;
+ tl#add_accel_group accel_group;
+ tl#show ()