use the real history in emacs

Ignore-this: 2ec438176bdf1549eb2b7c6c8f3f5e50

darcs-hash:20120212204029-c41ad-c5e71ad6e8d290b0c7e19994e3ba46879b38089d
This commit is contained in:
Jeremie Dimino 2012-02-12 21:40:29 +01:00
parent 8725e10070
commit 3fe28723e2
2 changed files with 138 additions and 105 deletions

View File

@ -374,11 +374,14 @@ module Emacs(M : sig end) = struct
(* Copy standard output, which will be used to send commands. *) (* Copy standard output, which will be used to send commands. *)
let command_oc = Unix.out_channel_of_descr (Unix.dup Unix.stdout) let command_oc = Unix.out_channel_of_descr (Unix.dup Unix.stdout)
let split_lines str = let split_at ?(trim=false) ch str =
let rec aux i j = let rec aux i j =
if j = String.length str then if j = String.length str then
[] if trim && i = j then
else if str.[j] = '\n' then []
else
[String.sub str i (j - i)]
else if str.[j] = ch then
String.sub str i (j - i) :: aux (j + 1) (j + 1) String.sub str i (j - i) :: aux (j + 1) (j + 1)
else else
aux i (j + 1) aux i (j + 1)
@ -455,7 +458,7 @@ module Emacs(M : sig end) = struct
| Some idx -> | Some idx ->
Some (String.sub line 0 idx, String.sub line (idx + 1) (String.length line - (idx + 1))) Some (String.sub line 0 idx, String.sub line (idx + 1) (String.length line - (idx + 1)))
let read_data ?(final_newline = true) () = let read_data () =
let buf = Buffer.create 1024 in let buf = Buffer.create 1024 in
let rec loop first = let rec loop first =
match read_command () with match read_command () with
@ -467,7 +470,6 @@ module Emacs(M : sig end) = struct
Buffer.add_string buf data; Buffer.add_string buf data;
loop false loop false
| Some ("end", _) -> | Some ("end", _) ->
if final_newline then Buffer.add_char buf '\n';
Buffer.contents buf Buffer.contents buf
| Some (command, argument) -> | Some (command, argument) ->
Printf.ksprintf (send "stderr") "'data' or 'end' command expected, got %S!" command; Printf.ksprintf (send "stderr") "'data' or 'end' command expected, got %S!" command;
@ -475,17 +477,19 @@ module Emacs(M : sig end) = struct
in in
loop true loop true
let process_input eos_is_error = let process_input add_to_history eos_is_error =
match parse_and_check (read_data ()) eos_is_error with let input = read_data () in
match parse_and_check input eos_is_error with
| UTop.Value phrase -> | UTop.Value phrase ->
send "accept" ""; send "accept" "";
if add_to_history then LTerm_history.add UTop.history input;
(* Add Lwt_main.run to toplevel evals. *) (* Add Lwt_main.run to toplevel evals. *)
let phrase = if UTop.get_auto_run_lwt () then insert_lwt_main_run phrase else phrase in let phrase = if UTop.get_auto_run_lwt () then insert_lwt_main_run phrase else phrase in
(* No exception can be raised at this stage. *) (* No exception can be raised at this stage. *)
ignore (Toploop.execute_phrase true Format.std_formatter phrase) ignore (Toploop.execute_phrase true Format.std_formatter phrase)
| UTop.Error (locs, msg) -> | UTop.Error (locs, msg) ->
send "accept" (String.concat "," (List.map (fun (a, b) -> Printf.sprintf "%d,%d" a b) locs)); send "accept" (String.concat "," (List.map (fun (a, b) -> Printf.sprintf "%d,%d" a b) locs));
List.iter (send "stderr") (split_lines msg) List.iter (send "stderr") (split_at ~trim:true '\n' msg)
let rec loop () = let rec loop () =
(* Reset completion. *) (* Reset completion. *)
@ -500,30 +504,30 @@ module Emacs(M : sig end) = struct
(* Tell emacs we are ready. *) (* Tell emacs we are ready. *)
send "prompt" ""; send "prompt" "";
loop_commands () loop_commands (LTerm_history.contents UTop.history) []
and loop_commands () = and loop_commands history_prev history_next =
match read_command () with match read_command () with
| None -> | None ->
() ()
| Some ("input", "allow-incomplete") -> | Some ("input", arg) ->
let args = split_at ',' arg in
let allow_incomplete = List.mem "allow-incomplete" args
and add_to_history = List.mem "add-to-history" args in
let continue = let continue =
try try
process_input false; process_input add_to_history (not allow_incomplete);
false false
with UTop.Need_more -> with UTop.Need_more ->
send "continue" ""; send "continue" "";
true true
in in
if continue then if continue then
loop_commands () loop_commands history_prev history_next
else else
loop () loop ()
| Some ("input", "") ->
process_input true;
loop ()
| Some ("complete", _) -> | Some ("complete", _) ->
let input = read_data ~final_newline:false () in let input = read_data () in
let start, words = UTop_complete.complete input in let start, words = UTop_complete.complete input in
let words = List.map fst words in let words = List.map fst words in
let prefix = LTerm_read_line.common_prefix words in let prefix = LTerm_read_line.common_prefix words in
@ -536,11 +540,33 @@ module Emacs(M : sig end) = struct
in in
if suffix = "" then begin if suffix = "" then begin
send "completion-start" ""; send "completion-start" "";
List.iter (fun word -> send "completion" word) words; List.iter (send "completion") words;
send "completion-stop" ""; send "completion-stop" "";
end else end else
send "completion-word" suffix; send "completion-word" suffix;
loop_commands () loop_commands history_prev history_next
| Some ("history-prev", _) -> begin
let input = read_data () in
match history_prev with
| [] ->
send "history-bound" "";
loop_commands history_prev history_next
| entry :: history_prev ->
List.iter (send "history-data") (split_at '\n' entry);
send "history-end" "";
loop_commands history_prev (input :: history_next)
end
| Some ("history-next", _) -> begin
let input = read_data () in
match history_next with
| [] ->
send "history-bound" "";
loop_commands history_prev history_next
| entry :: history_next ->
List.iter (send "history-data") (split_at '\n' entry);
send "history-end" "";
loop_commands (input :: history_prev) history_next
end
| Some (command, _) -> | Some (command, _) ->
Printf.ksprintf (send "stderr") "unrecognized command %S!" command; Printf.ksprintf (send "stderr") "unrecognized command %S!" command;
exit 1 exit 1
@ -641,19 +667,22 @@ let common_init () =
(* Make sure SIGINT is catched while executing OCaml code. *) (* Make sure SIGINT is catched while executing OCaml code. *)
Sys.catch_break true; Sys.catch_break true;
(* Load user's .ocamlinit file. *) (* Load user's .ocamlinit file. *)
match !Clflags.init_file with (match !Clflags.init_file with
| Some fn -> | Some fn ->
if Sys.file_exists fn then if Sys.file_exists fn then
ignore (Toploop.use_silently Format.err_formatter fn) ignore (Toploop.use_silently Format.err_formatter fn)
else else
Printf.eprintf "Init file not found: \"%s\".\n" fn Printf.eprintf "Init file not found: \"%s\".\n" fn
| None -> | None ->
if Sys.file_exists ".ocamlinit" then if Sys.file_exists ".ocamlinit" then
ignore (Toploop.use_silently Format.err_formatter ".ocamlinit") ignore (Toploop.use_silently Format.err_formatter ".ocamlinit")
else else
let fn = Filename.concat LTerm_resources.home ".ocamlinit" in let fn = Filename.concat LTerm_resources.home ".ocamlinit" in
if Sys.file_exists fn then if Sys.file_exists fn then
ignore (Toploop.use_silently Format.err_formatter fn) ignore (Toploop.use_silently Format.err_formatter fn));
(* Load history after the initialization file so the user can change
the history file name. *)
Lwt_main.run (init_history ())
let load_inputrc () = let load_inputrc () =
try_lwt try_lwt
@ -684,9 +713,6 @@ let main_aux () =
Lwt_main.run (welcome term); Lwt_main.run (welcome term);
(* Common initialization. *) (* Common initialization. *)
common_init (); common_init ();
(* Load history after the initialization file so the user can
change the history file name. *)
Lwt_main.run (init_history ());
(* Print help message. *) (* Print help message. *)
print_string "\nType #utop_help for help about using utop.\n\n"; print_string "\nType #utop_help for help about using utop.\n\n";
flush stdout; flush stdout;

View File

@ -146,15 +146,6 @@ This hook is only run if exiting actually kills the buffer."
(defvar utop-command-number 0 (defvar utop-command-number 0
"The number of the current command.") "The number of the current command.")
(defvar utop-history nil
"The history of typed command.")
(defvar utop-history-prev nil
"The history before the cursor.")
(defvar utop-history-next nil
"The history after the cursor.")
(defvar utop-completion nil (defvar utop-completion nil
"Current completion.") "Current completion.")
@ -167,6 +158,7 @@ before the end of prompt.")
- edit: the user is typing a command - edit: the user is typing a command
- comp: waiting for completion - comp: waiting for completion
- hist: waiting for history
- wait: ocaml is evaluating a phrase - wait: ocaml is evaluating a phrase
- done: ocaml has died.") - done: ocaml has died.")
@ -176,8 +168,8 @@ before the end of prompt.")
(defvar utop-phrase-terminator ";;" (defvar utop-phrase-terminator ";;"
"The OCaml phrase terminator.") "The OCaml phrase terminator.")
(defvar utop-pending-input nil (defvar utop-pending-entry nil
"The phrase to add to history if it is accepted by OCaml.") "History entry")
(defvar utop-pending-position nil (defvar utop-pending-position nil
"The position of the cursor in the phrase sent to OCaml (where "The position of the cursor in the phrase sent to OCaml (where
@ -204,7 +196,7 @@ to add the newline character if it is not accepted).")
(select-window window) (select-window window)
(goto-char (point-max))))))) (goto-char (point-max)))))))
(defun set-utop-state (state) (defun utop-set-state (state)
"Change the utop state and mode-line-process." "Change the utop state and mode-line-process."
(setq utop-state state) (setq utop-state state)
(setq mode-line-process (setq mode-line-process
@ -213,6 +205,8 @@ to add the newline character if it is not accepted).")
": idle") ": idle")
((eq state 'comp) ((eq state 'comp)
": completion") ": completion")
((eq state 'hist)
": history")
((eq state 'wait) ((eq state 'wait)
": running") ": running")
((eq state 'done) ((eq state 'done)
@ -233,6 +227,18 @@ to add the newline character if it is not accepted).")
(t (t
": unknown")))) ": unknown"))))
(defun utop-send-data (cmd)
"Send current input to utop"
(let ((lines (split-string (buffer-substring-no-properties utop-prompt-max (point-max)) "\n")))
;; Send all lines to utop
(process-send-string utop-process cmd)
(while lines
;; Send the line
(process-send-string utop-process (concat "data:" (car lines) "\n"))
;; Remove it and continue
(setq lines (cdr lines)))
(process-send-string utop-process "end:\n")))
;; +-----------------------------------------------------------------+ ;; +-----------------------------------------------------------------+
;; | Edition control | ;; | Edition control |
;; +-----------------------------------------------------------------+ ;; +-----------------------------------------------------------------+
@ -244,7 +250,9 @@ to add the newline character if it is not accepted).")
((eq utop-state 'done) ((eq utop-state 'done)
(signal 'text-read-only '("You cannot edit the buffer when ocaml is not running"))) (signal 'text-read-only '("You cannot edit the buffer when ocaml is not running")))
((eq utop-state 'comp) ((eq utop-state 'comp)
(signal 'text-read-only '("You cannot edit the buffer while waiting for completion"))))) (signal 'text-read-only '("You cannot edit the buffer while waiting for completion")))
((eq utop-state 'comp)
(signal 'text-read-only '("You cannot edit the buffer while waiting for history")))))
(defun utop-before-change (start stop) (defun utop-before-change (start stop)
(unless utop-inhibit-check (unless utop-inhibit-check
@ -278,25 +286,19 @@ to add the newline character if it is not accepted).")
"Go to the previous entry of the history." "Go to the previous entry of the history."
(interactive) (interactive)
(with-current-buffer utop-buffer-name (with-current-buffer utop-buffer-name
(when (and (eq utop-state 'edit) utop-history-prev) (when (eq utop-state 'edit)
;; Push current input after the history cursor (utop-set-state 'hist)
(push (delete-and-extract-region utop-prompt-max (point-max)) utop-history-next) (setq utop-pending-entry nil)
;; Go to after the prompt to insert the previous input (utop-send-data "history-prev:\n"))))
(goto-char utop-prompt-max)
;; Pop one element from history before the cursor and insert it
(insert (pop utop-history-prev)))))
(defun utop-history-goto-next () (defun utop-history-goto-next ()
"Go to the next entry of the history." "Go to the next entry of the history."
(interactive) (interactive)
(with-current-buffer utop-buffer-name (with-current-buffer utop-buffer-name
(when (and (eq utop-state 'edit) utop-history-next) (when (eq utop-state 'edit)
;; Push current input before the history cursor (utop-set-state 'hist)
(push (delete-and-extract-region utop-prompt-max (point-max)) utop-history-prev) (setq utop-pending-entry nil)
;; Go to after the prompt to insert the next input (utop-send-data "history-next:\n"))))
(goto-char utop-prompt-max)
;; Pop one element from history after the cursor and insert it
(insert (pop utop-history-next)))))
;; +-----------------------------------------------------------------+ ;; +-----------------------------------------------------------------+
;; | Receiving input from the utop sub-process | ;; | Receiving input from the utop sub-process |
@ -331,7 +333,7 @@ to add the newline character if it is not accepted).")
;; Make everything before the end prompt read-only ;; Make everything before the end prompt read-only
(add-text-properties (point-min) utop-prompt-max utop-non-editable-properties) (add-text-properties (point-min) utop-prompt-max utop-non-editable-properties)
;; We are now editing ;; We are now editing
(set-utop-state 'edit) (utop-set-state 'edit)
;; Move the point to the end of buffer in all utop windows ;; Move the point to the end of buffer in all utop windows
(utop-goto-point-max-all-windows)) (utop-goto-point-max-all-windows))
@ -363,9 +365,6 @@ to add the newline character if it is not accepted).")
;; A new prompt ;; A new prompt
((string= command "prompt") ((string= command "prompt")
(let ((prompt (apply utop-prompt ()))) (let ((prompt (apply utop-prompt ())))
;; Reset history
(setq utop-history-prev utop-history)
(setq utop-history-next nil)
;; Insert the new prompt ;; Insert the new prompt
(utop-insert-prompt prompt) (utop-insert-prompt prompt)
;; Increment the command number ;; Increment the command number
@ -374,15 +373,10 @@ to add the newline character if it is not accepted).")
(when utop-initial-command (when utop-initial-command
(goto-char (point-max)) (goto-char (point-max))
(insert utop-initial-command) (insert utop-initial-command)
(utop-insert-phrase-terminator)
(setq utop-initial-command nil) (setq utop-initial-command nil)
(utop-eval-input)))) (utop-eval-input nil t nil))))
;; Input has been accepted ;; Input has been accepted
((string= command "accept") ((string= command "accept")
;; Push input to the history if it is different from the top
;; of the history
(when (or (null utop-history) (not (string= utop-pending-input (car utop-history))))
(push utop-pending-input utop-history))
;; Add a newline character at the end of the buffer ;; Add a newline character at the end of the buffer
(goto-char (point-max)) (goto-char (point-max))
(insert "\n") (insert "\n")
@ -408,10 +402,30 @@ to add the newline character if it is not accepted).")
(goto-char (+ utop-prompt-max utop-pending-position)) (goto-char (+ utop-prompt-max utop-pending-position))
(insert "\n")) (insert "\n"))
;; Reset the state ;; Reset the state
(set-utop-state 'edit)) (utop-set-state 'edit))
;; Part of a history entry
((string= command "history-data")
(cond
(utop-pending-entry
(setq utop-pending-entry (concat utop-pending-entry "\n" argument)))
(t
(setq utop-pending-entry argument))))
;; End of history data
((string= command "history-end")
(goto-char utop-prompt-max)
;; Delete current input
(delete-region utop-prompt-max (point-max))
;; Insert entry
(insert utop-pending-entry)
;; Resume edition
(utop-set-state 'edit))
;; We are at a bound of history
((string= command "history-bound")
;; Just resume edition
(utop-set-state 'edit))
;; Complete with a word ;; Complete with a word
((string= command "completion-word") ((string= command "completion-word")
(set-utop-state 'edit) (utop-set-state 'edit)
(insert argument) (insert argument)
;; Hide completion ;; Hide completion
(minibuffer-hide-completions)) (minibuffer-hide-completions))
@ -423,7 +437,7 @@ to add the newline character if it is not accepted).")
(push argument utop-completion)) (push argument utop-completion))
;; End of completion ;; End of completion
((string= command "completion-stop") ((string= command "completion-stop")
(set-utop-state 'edit) (utop-set-state 'edit)
(with-output-to-temp-buffer "*Completions*" (with-output-to-temp-buffer "*Completions*"
(display-completion-list (nreverse utop-completion))) (display-completion-list (nreverse utop-completion)))
(setq utop-completion nil))))) (setq utop-completion nil)))))
@ -450,7 +464,7 @@ to add the newline character if it is not accepted).")
;; | Sending data to the utop sub-process | ;; | Sending data to the utop sub-process |
;; +-----------------------------------------------------------------+ ;; +-----------------------------------------------------------------+
(defun utop-eval-input (&optional allow-incomplete auto-end) (defun utop-eval-input (&optional allow-incomplete auto-end add-to-history)
"Send the current input to the utop process and let ocaml "Send the current input to the utop process and let ocaml
evaluate it. evaluate it.
@ -460,7 +474,9 @@ continue.
If AUTO-END is non-nill then ALLOW-INCOMPLETE is ignored and a If AUTO-END is non-nill then ALLOW-INCOMPLETE is ignored and a
phrase terminator (;; or ; if using revised syntax) will be phrase terminator (;; or ; if using revised syntax) will be
automatically inserted by utop." automatically inserted by utop.
If ADD-TO-HISTORY is t then the input will be added to history."
(interactive) (interactive)
(with-current-buffer utop-buffer-name (with-current-buffer utop-buffer-name
(when (eq utop-state 'edit) (when (eq utop-state 'edit)
@ -477,30 +493,26 @@ automatically inserted by utop."
;; character at the end ;; character at the end
(when (< utop-pending-position 0) (when (< utop-pending-position 0)
(setq utop-pending-position (- (point) utop-prompt-max))))) (setq utop-pending-position (- (point) utop-prompt-max)))))
(let* ((input (buffer-substring-no-properties utop-prompt-max (point-max))) ;; We are now waiting for ocaml
(lines (split-string input "\n"))) (utop-set-state 'wait)
;; Save for history (utop-send-data
(setq utop-pending-input input) (cond
;; We are now waiting for ocaml ((and allow-incomplete (not auto-end) add-to-history)
(set-utop-state 'wait) "input:allow-incomplete,add-to-history\n")
;; Send all lines to utop (add-to-history
(process-send-string utop-process (if (and allow-incomplete (not auto-end)) "input:allow-incomplete\n" "input:\n")) "input:add-to-history\n")
(while lines (t
;; Send the line "input:\n"))))))
(process-send-string utop-process (concat "data:" (car lines) "\n"))
;; Remove it and continue
(setq lines (cdr lines)))
(process-send-string utop-process "end:\n")))))
(defun utop-eval-input-or-newline () (defun utop-eval-input-or-newline ()
"Same as (`utop-eval-input' t nil)." "Same as (`utop-eval-input' t nil t)."
(interactive) (interactive)
(utop-eval-input t nil)) (utop-eval-input t nil t))
(defun utop-eval-input-auto-end () (defun utop-eval-input-auto-end ()
"Same as (`utop-eval-input' nil t)." "Same as (`utop-eval-input' nil t t)."
(interactive) (interactive)
(utop-eval-input nil t)) (utop-eval-input nil t t))
;; +-----------------------------------------------------------------+ ;; +-----------------------------------------------------------------+
;; | Completion | ;; | Completion |
@ -516,7 +528,7 @@ automatically inserted by utop."
;; Split it ;; Split it
(let ((lines (split-string input "\n"))) (let ((lines (split-string input "\n")))
;; We are now waiting for completion ;; We are now waiting for completion
(set-utop-state 'comp) (utop-set-state 'comp)
;; Send all lines to utop ;; Send all lines to utop
(process-send-string utop-process "complete:\n") (process-send-string utop-process "complete:\n")
(while lines (while lines
@ -580,7 +592,7 @@ automatically inserted by utop."
(insert text) (insert text)
;; Send input to utop now, telling it to automatically add the ;; Send input to utop now, telling it to automatically add the
;; phrase terminator ;; phrase terminator
(utop-eval-input nil t)) (utop-eval-input nil t nil))
((eq utop-state 'wait) ((eq utop-state 'wait)
;; utop is starting, save the initial command to send ;; utop is starting, save the initial command to send
(setq utop-initial-command text)))))) (setq utop-initial-command text))))))
@ -669,7 +681,7 @@ To automatically do that just add these lines to your .emacs:
(let ((status (process-status utop-process))) (let ((status (process-status utop-process)))
(when (or (eq status 'exit) (eq status 'signal)) (when (or (eq status 'exit) (eq status 'signal))
;; The process is terminated ;; The process is terminated
(set-utop-state 'done) (utop-set-state 'done)
(let ((exit-code (process-exit-status utop-process))) (let ((exit-code (process-exit-status utop-process)))
(utop-perform (utop-perform
;; Insert a message at the end ;; Insert a message at the end
@ -786,7 +798,6 @@ To automatically do that just add these lines to your .emacs:
(setq utop-prompt-max (point-max)) (setq utop-prompt-max (point-max))
(setq utop-output "") (setq utop-output "")
(setq utop-command-number 0) (setq utop-command-number 0)
(setq utop-pending nil)
(setq utop-completion nil) (setq utop-completion nil)
;; Set the state to done to allow utop to be restarted if ;; Set the state to done to allow utop to be restarted if
@ -798,7 +809,7 @@ To automatically do that just add these lines to your .emacs:
;; Set the initial state: we are waiting for ocaml to send the ;; Set the initial state: we are waiting for ocaml to send the
;; initial prompt ;; initial prompt
(set-utop-state 'wait) (utop-set-state 'wait)
;; Filter the output of the sub-process with our filter function ;; Filter the output of the sub-process with our filter function
(set-process-filter utop-process 'utop-process-output) (set-process-filter utop-process 'utop-process-output)
@ -823,16 +834,12 @@ To automatically do that just add these lines to your .emacs:
(make-local-variable 'utop-last-prompt) (make-local-variable 'utop-last-prompt)
(make-local-variable 'utop-output) (make-local-variable 'utop-output)
(make-local-variable 'utop-command-number) (make-local-variable 'utop-command-number)
(make-local-variable 'utop-history)
(make-local-variable 'utop-history-prev)
(make-local-variable 'utop-history-next)
(make-local-variable 'utop-pending)
(make-local-variable 'utop-inhibit-check) (make-local-variable 'utop-inhibit-check)
(make-local-variable 'utop-state) (make-local-variable 'utop-state)
(make-local-variable 'utop-initial-command) (make-local-variable 'utop-initial-command)
(make-local-variable 'utop-phrase-terminator) (make-local-variable 'utop-phrase-terminator)
(make-local-variable 'utop-pending-input)
(make-local-variable 'utop-pending-position) (make-local-variable 'utop-pending-position)
(make-local-variable 'utop-pending-entry)
;; Set the major mode ;; Set the major mode
(setq major-mode 'utop-mode) (setq major-mode 'utop-mode)