Warning: this is an htmlized version!
The original is here, and
the conversion rules are here.
;;; eev-steps.el -- single-steppers.

;; Copyright (C) 2004,2005,2006,2008,2010 Free Software
;; Foundation, Inc.
;;
;; This file is part of GNU eev.
;;
;; GNU eev is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version.
;;
;; GNU eev is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING.  If not, write to the
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
;; Boston, MA 02111-1307, USA.
;;
;; Author:     Eduardo Ochs <[email protected]>
;; Maintainer: Eduardo Ochs <[email protected]>
;; Version:    2010sep10
;; Keywords:   e-scripts, help, hyperlinks, hypertext, processes,
;;             shell, tex

;;; Commentary:

;; The definition in this file are mostly obsolete - there are
;; better-factored versions of most functions at "eev-mini-steps.el".
;; However, the comments here are in many cases better than the
;; comments there...
;;
;; "eev-steps.el" and "eev-mini-steps.el" will be merged at some point
;; (hopefully soon!), but at this moment they are both loaded by
;; "eev-all.el": first "eev-steps.el" - this file -, then
;; "eev-mini-steps.el", that overrides many of the definitions...
;;
;; See:
;;   (find-eev "eev-mini-steps.el")
;;   (find-eev "eev-all.el")
;;   (find-eevsh "./eev-rctool")
;;   (find-eevsh "./eev-rctool new_block_emacs")

;; Stuff that I wrote in 2006aug17:
;;
;; States
;; ======
;;
;; Think of Emacs as being a huge Turing Machine in which some of the
;; memory cells control what is shown on the screen. Then, following
;; an elisp hyperlink - like, say, (find-node "(elisp)Undo") - means
;; going from one Emacs state to another one, in a way that is usually
;; reversible; killing or burying the new Info buffer will in most
;; cases make Emacs display what it was displaying before.
;; 
;; In a sense, (find-node "(elisp)Undo") "finds" - like `find-file' does,
;; and like web browsers do - an Info node; in another sense, it "finds"
;; a new Emacs state, in which the specified Info node is shown on the
;; current buffer.
;; 
;; Sometimes we want to "find" - or rather, to "find out" - the effect a
;; certain series of operations; we just want to apply those operation to
;; the current Emacs state to go to another state. For example,
;; 
;;  (find-eek "C-x 4 C-h")
;; 
;; finds the "effect of the key sequence" `C-x 4 C-h'. Incidentally, this
;; creates a buffer with some hyperlinks, and it is an operation that is
;; as reversible as a `find-file' or `find-node' - but this doesn't need
;; to be the case always.
;; 
;; A `progn' expression can also be seen as an elisp hyperlink to a new
;; Emacs state.
;;
;;
;; The steppers
;; ============
;; Sometimes we have series of steps that we want to execute, but for one
;; reason or another we have to execute those steps one by one, not all
;; at once; what we do is that we "program" the list of steps somehow,
;; and we create a button - actually a key; we bind a command to it -
;; that when we press it Emacs interprets it as meaning "now execute the
;; next step in the list".
;;
;; Currently there are four different steppers, and they are not unified.
;; They implement their "get the next step from the list" things in
;; different ways, and they use different keys as their "Next" buttons.
;; Let me explain them one by one, in historical order, starting by the
;; ones that appeared earlier.
;;
;;  `ee-yank-one-line' - the last killed string is considered as a
;;    list of steps; in this case "get the next step" means "remove
;;    the first line from the string at the top of the kill ring",
;;    and "execute the step" means insert the removed line at point
;;    and then run the command bound to RET. This is useful, for
;;    example, to send several lines in sequence to an `M-x shell' or
;;    `M-x eshell' buffer as if we were typing the lines one by one.
;;  `eesteps'
;;  `eechannel'
;;  `eepitch'

;; The rest was written in 2006jan10 or earlier.

;; See:
;; http://article.gmane.org/gmane.emacs.eev.devel/28
;; http://article.gmane.org/gmane.emacs.eev.devel/32
;; http://article.gmane.org/gmane.emacs.eev.devel/33
;; http://lists.gnu.org/archive/html/eev/2005-12/msg00003.html
;; http://lists.gnu.org/archive/html/eev/2005-12/msg00007.html
;; http://lists.gnu.org/archive/html/eev/2005-12/msg00009.html
;; http://lists.gnu.org/archive/html/eev/2006-01/msg00000.html




(defvar eesteps-pos 0)
(defvar eesteps-list ())
(defvar eechannel-default nil)
(defvar eepitch-code '(error "eepitch not set up"))
(defvar eepitch-target-buffer nil)

;; Hey, I'm not sure if the code for the help page should be here...

(defvar eev-help-page-file-name "$EEVTMPDIR/HELP")
(defvar eev-help-previous-buffer nil
  "Non-nil when we're on a help page buffer. Used by `eev-help-page'.")

(make-variable-buffer-local 'eev-help-previous-buffer)

(defun ee-bol () (point-at-bol))
(defun ee-eol () (point-at-eol))
(defun ee-eval-string (str)
  "Wrap STR in a progn then read it and eval it.
Examples: (ee-eval-string \"(+ 1 2) (* 3 4) ;; this returns 12=3*4\")
          (ee-eval-string \";; this returns nil\")"
  (eval (read (concat "(progn\n" str "\n)"))))





;;;                 _                 
;;;   ___  ___  ___| |_ ___ _ __  ___ 
;;;  / _ \/ _ \/ __| __/ _ \ '_ \/ __|
;;; |  __/  __/\__ \ ||  __/ |_) \__ \
;;;  \___|\___||___/\__\___| .__/|___/
;;;                        |_|        
;;;
;;; hyperlinks to key sequences and series of Emacs actions
;;;

(defun eesteps (list)
  "Set the LIST of steps that `eesteps-do-step' will execute.\n
Here's an example: run\n
  (eesteps '(\"C-x b * scratch * RET   ;;; change to the buffer *scratch*\"
             \"foobar\"
             \"3*<left>\"
             (insert \"!\")))\n
then type \\[eesteps-do-step] four times.\n
Each step is either a string -- meaning a series of keys, in the
format used by `edmacro-mode' -- or a sexp to be evaluated."
  (setq eesteps-pos 0)
  (setq eesteps-list list)
  `(,(length list) steps stored - use <f12> to execute a step))

(defun eek (s &optional e count)
  "Execute the region between S and E (or the string S) as a keyboard macro.
See `edmacro-mode' for the exact format.\n
An example: (eek \"C-x 4 C-h\")"
  (interactive "r")
  (execute-kbd-macro (read-kbd-macro (ee-se-to-string s e)) count))

(defun eek0 (kbmacro &optional count)
  "This is similar to `eek', but uses the low-level formats for macros.
Example: (eek \"\\C-x4\\C-h\")"
  (execute-kbd-macro kbmacro count))

(defun eesteps-perform (step &rest rest)
  (if (stringp step)
      (eek step)
    (eval step))
  (if rest (apply 'eesteps-eval rest)))

(defun eesteps-do-step (&optional arg)
  (interactive "P")
  (if (>= eesteps-pos (length eesteps-list))
      (error "No more steps"))
  (if (eq arg 0)
      (message "Next step: %d = %S" eesteps-pos (nth eesteps-pos eesteps-list))
    (eesteps-perform (nth eesteps-pos eesteps-list))
    (setq eesteps-pos (1+ eesteps-pos))))

;; (defun eesteps-do-step ()
;;   (interactive)
;;   (if (>= eesteps-pos (length eesteps-list))
;;       (error "No more steps"))
;;   (let ((step (nth eesteps-pos eesteps-list)))
;;     (cond ((stringp step) (eek step))
;;           (t (eval step))))
;;   (setq eesteps-pos (1+ eesteps-pos)))




;;;                                           _     _      
;;;   ___  _____   __     _ __   _____      _| |__ (_) ___ 
;;;  / _ \/ _ \ \ / /____| '_ \ / _ \ \ /\ / / '_ \| |/ _ \
;;; |  __/  __/\ V /_____| | | |  __/\ V  V /| |_) | |  __/
;;;  \___|\___| \_/      |_| |_|\___| \_/\_/ |_.__/|_|\___|
;;;                                                        
(defun eev-newbie ()
  (interactive)
  (setq debug-on-error nil)
  (setq eval-expression-debug-on-error nil)
  ;; (setq pop-up-windows nil)
  (eev-mode 1)
  (message "Newbie settings activated.  Have you tried `M-x eev-demos'?"))


;;;                           _                          
;;;   ___  _____   __      __| | ___ _ __ ___   ___  ___ 
;;;  / _ \/ _ \ \ / /____ / _` |/ _ \ '_ ` _ \ / _ \/ __|
;;; |  __/  __/\ V /_____| (_| |  __/ | | | | | (_) \__ \
;;;  \___|\___| \_/       \__,_|\___|_| |_| |_|\___/|___/
;;;                                                      
;;; eev-newbie and eev-demos
;;;

(defun eekl (str &rest rest)
  (eek0 (concat str "\r"))
  (if rest (apply 'eekl rest)))

(defun eekv (str) (eek str) (message str))

;; A hack for showing the region (temporarily) in Emacs <= 21.1.3.
;; These functions are used by some demos. They may be removed in the future.
;; Note that in recent Emacsen C-SPC C-SPC <movement> highlights the region:
;; (find-efile "ChangeLog" "temporary transient-mark-mode")
;; (find-eetcfile "NEWS" "C-SPC C-SPC; this enables Transient Mark mode")
;;
(defun eekr  (str) (eek str)  (eeflash (point) (mark)))
(defun eekvr (str) (eekv str) (eeflash (point) (mark)))

(defun eev-demos (arg)
  (interactive "P")
  (find-eevexfile "demos.e")
  (if (and arg (>= arg 1) (<= arg 5))
      (progn (ee-goto-position (format "\n;; End of demo %d\n" arg))
	     (forward-line -2)
	     (message "Type M-e to load the demo above the cursor."))
    (let ((message-truncate-lines nil))
      (message (concat "Use `M-1 M-x eev-demos' to go to the 1st demo,\n"
		       "`M-2 M-x eev-demos' for the 2nd demo, etc (up to 4).")))))


;;;  _          _                                
;;; | |__   ___| |_ __    _ __   __ _  __ _  ___ 
;;; | '_ \ / _ \ | '_ \  | '_ \ / _` |/ _` |/ _ \
;;; | | | |  __/ | |_) | | |_) | (_| | (_| |  __/
;;; |_| |_|\___|_| .__/  | .__/ \__,_|\__, |\___|
;;;              |_|     |_|          |___/      
;;;
;;; eev-help-page
;;;

(defun eev-help-page ()
  "Switch to the eev help page buffer, or, if we're already on it, switch back."
  (interactive)
  (if eev-help-previous-buffer		       ; are we on a help page buffer?
      (if (buffer-live-p eev-help-previous-buffer)    ; can we go back?
	  (switch-to-buffer eev-help-previous-buffer) ; yes: switch back
	(bury-buffer))				      ; no: bury the help buffer
    (let ((buffer (current-buffer)))	       ; we're not on a help page bufer.
      (find-fline eev-help-page-file-name)     ; visit the help file
      (setq eev-help-previous-buffer buffer)))) ; and mark it as a help buffer



;;;
;;; more tools
;;;

;; ee-flatten is obsolte - using backticks is better

;; (ee-flatten '((1 2 3) (4 5) (((6)) 7) nil nil 8 9))
;; (ee-flatten '(1 2 3) '(4 5) '(((6)) 7) nil nil 8 9)
;;
(defun ee-flatten (obj &rest rest)
  (cond (rest (append (ee-flatten obj) (ee-flatten rest)))
	((null obj) nil)
	((listp obj) (append (ee-flatten (car obj)) (ee-flatten (cdr obj))))
	(t (list obj))))

(defun ee-read-file (fname)
  (with-temp-buffer
    (insert-file-contents fname)
    (buffer-string)))

(defun ee-no-trailing-nl (str)
  (replace-regexp-in-string "\n$" "" str))



;;;                    _    
;;;  _   _  __ _ _ __ | | __
;;; | | | |/ _` | '_ \| |/ /
;;; | |_| | (_| | | | |   < 
;;;  \__, |\__,_|_| |_|_|\_\
;;;  |___/                  
;;;
;;; yanking lines one of a time inside emacs
;;;

(defun ee-yank-one-line ()
  "Yank the first line of the killed text and do a RET.
Insert the first line from the latest kill-ring entry and run the
action associated to the RET key. The kill-ring entry is then
altered so that subsequent calls of yank-first-line will yank
successive lines.

As a special case, if the first line of the kill starts with
\"*\" then evaluate the rest of it instead of yanking it, using
`ee-eval-string'.

For an example of usage see: (find-efunctiondescr 'eestore)"
  (interactive)
  (let ((bigstr (car kill-ring)))
    (if (equal bigstr "") (error "No more lines"))
    (string-match "^\\([^\n]*\\)\\(\n\\|$\\)" bigstr)
    (let ((line (match-string 1 bigstr))           ; first line from the kill
	  (rest (substring bigstr (match-end 0)))) ; rest of the kill
      (if (string-match "^*\\(.*\\)" line)         ; lines with a red star
	  (ee-eval-string (match-string 1 line))   ; are eval'ed
	(insert line)			           ; other lines are "typed"
	(call-interactively (key-binding "\r")))   ; and then we do a RET
      (setcar kill-ring rest))))		   ; remove the first line

(defun eestore (s &optional e)
  "Store the region between S and E in the kill ring.
This is especially useful in conjunction with `ee-yank-one-line'.
Here's an example; to run it eval the `eestore-bounded', then
type `\\[ee-yank-one-line]' several times.

#*
* (shell) ;; (eestore-bounded)
echo $[1+2]
echo foo
#*
"
  (kill-new (ee-se-to-string s e))
  (format "Stored in the kill-ring"))

(eeb-define 'eestore-bounded  'eestore 'ee-delimiter-hash  nil t t)




;;;
;;; Sending lines to processes running in other Emacs buffers
;;;

;; NOTE: this is Rubikitch's "eethrow" function, only a bit less fragile.
;; See: http://lists.gnu.org/archive/html/eev/2005-12/msg00007.html
;;      http://article.gmane.org/gmane.emacs.eev.devel/32

(defun eepitch-prepare (code)
"Run CODE, remember its buffer, then split the frame to show also that buffer.
This function is usually called through the macro `eepitch'.
See `eepitch-this-line' for an example of use."
  (let ((pop-up-windows t)
	(same-window-buffer-names nil))
    (save-window-excursion
      (setq eepitch-code code)
      (eval code)
      (setq eepitch-target-buffer (current-buffer)))
    (display-buffer eepitch-target-buffer)))

(defmacro eepitch (code)
"Call `eepitch-prepare'; this macro quotes its argument CODE, just like setq.
See `eepitch-this-line' for an example of use."
  `(eepitch-prepare ',code))

(defun eepitch-this-line ()
"Send the current line to another buffer (in another window).
When the line starts with \"*\" execute it as Lisp instead of sending it.
If the target buffer (stored in `eepitch-target-buffer') is not
visible then make it become visible again, by running the code
stored in `eepitch-code' with `eepitch-prepare'.
Here is an example (execute each line with \\[eepitch-this-line]):\n
* (eepitch (shell))
echo foo
* (message (format \"%S\" `(eepitch ,eepitch-code)))
* (delete-other-windows)
echo bar\n\n"
  (interactive)
  (let ((line (buffer-substring (ee-bol) (ee-eol)))) ; contents of this line
    (if (string-match "^*\\(.*\\)" line)             ; lines with a red star
        (ee-eval-string (match-string 1 line))       ; are eval'ed
      (if (not (get-buffer-window eepitch-target-buffer))  ; if we need then
	  (eepitch-prepare eepitch-code))   ; make the target window visible
      (save-selected-window
	(select-window (get-buffer-window eepitch-target-buffer))
	(insert line)                              ; other lines are "typed"
	(call-interactively (key-binding "\r"))))  ; and then we do a RET
    (ee-next-line 1)))



;;;
;;; starting background processes
;;;

(defun eebg-channel-xterm (channel &optional prog-and-args xterm-args)
  "This is the low-level way of creating an xterm listening on channel CHANNEL.
See `eechannel-xterm'."
  (interactive "sChannel: ")
  (apply 'start-process (format "xterm (channel %s)" channel) "*Messages*"
	 (ee-flatten
	  "xterm" "-T" (concat "channel " channel) xterm-args "-e"
	  (ee-expand "$EEVDIR/eegchannel") channel
	  (or prog-and-args (ee-expand "$SHELL")))))



;;;
;;; sending strings to external programs through "channels"
;;;

(defun eechannel-pidfile (channel)
  (ee-expand (format "$EEVTMPDIR/eeg.%s.pid" channel)))
(defun eechannel-strfile (channel)
  (ee-expand (format "$EEVTMPDIR/eeg.%s.str" channel)))

(defun eechannel-send (channel str)
  (if (not channel) (setq channel eechannel-default))
  (ee-write str nil "" "" (eechannel-strfile channel))
  (find-sh0 (format "kill -USR1 $(cat %s)" (eechannel-pidfile channel))))

(defun eechannel (channel &optional str)
  (interactive "sDefault channel: ")
  (if (not str)
      (setq eechannel-default channel)
    (eechannel-send channel str)))

(defun eechannel-do-this-line () (interactive)
  (let ((line (buffer-substring (ee-bol) (ee-eol)))) ; contents of this line
    (if (string-match "^*\\(.*\\)" line)             ; lines with a red star
	(ee-eval-string (match-string 1 line))       ; are eval'ed
      (eechannel-send nil (concat line "\n")))       ; other lines are sent
    (ee-next-line 1)))			             ; go down

(defun eech (s &optional e)		; bad name?
  (interactive "r")
  (eechannel-send eechannel-default (ee-se-to-string s e)))

(eeb-define 'eech-bounded 'eech 'ee-delimiter-hash nil t t)



;;;
;;; sending strings through "channels": high-level functions
;;;

(defun ee-pid-running-p (pid)
  "Return t if a process with pid PID is running. This is linux-specific.
This function just checks if a file /proc/<pid> exists.

If you need something that is kernel-independent then the
following idea might work: run \"ps PID\", discard the output,
test its exit code."
  (file-exists-p (format "/proc/%s" pid)))

(defun eechannel-pid (channel)
  "Note: this function returns either a pid (as a string) or nil."
  (let ((pidfile (eechannel-pidfile channel)))
    (if (file-exists-p pidfile) (ee-no-trailing-nl (ee-read-file pidfile)))))

(defun eechannel-running-p (channel)
  "Returns t if there is a process listening on CHANNEL."
  (let ((pid (eechannel-pid channel)))
    (if pid (ee-pid-running-p pid))))

(defun eechannel-xterm (channel &optional prog-and-args xterm-args)
  "If there's no process listening on CHANNEL then create an xterm there.
This function always sets the default channel to CHANNEL.
PROG-AND-ARGS and XTERM-ARGS are lists of strings: see `eebg-channel-xterm'."
  (interactive "sChannel: ")
  (eechannel channel)
  (if (eechannel-running-p channel)
      (message "Reusing channel %s" channel)
    (eebg-channel-xterm channel prog-and-args xterm-args)))

(defun eechannel-kill (channel)
  "Kill the process associated to channel CHANNEL."
  (find-sh0 (format "kill -9 $(cat %s)" (eechannel-pidfile channel))))



;;;
;;; sending blocks through channels
;;;

(defun eevnow (s &optional e)
  (interactive "r")
  (eev s e)
  (eech "ee\n"))

(eeb-define 'eevnow-bounded 'eevnow 'ee-delimiter-hash nil t t)

(defmacro ee-at (anchor &rest body)
  `(save-excursion
     (ee-goto-position (format ee-anchor-format ,anchor))
     . ,body))

(defmacro ee-at-file (fname anchor &rest body)
  `(with-current-buffer (find-file-noselect (ee-expand ,fname))
     (ee-goto-position (format ee-anchor-format ,anchor))
     . ,body))

(defun eevnow-at (anchor)
"Move temporarily to the anchor ANCHOR and run `eevnow-bounded' there.
If EE-ARG is non-nil then work as a hyperlink instead: just jump to ANCHOR.
This function is typically used in red-star lines inside F9-able blocks:
a line like\n
* (eevnow-at \"anchorname\")\n
works as a kind of subroutine call when executed with F9 (when F9 is
bound to `eechannel-do-this-line'), and as a hyperlink when run with,
say, M-2 M-2 M-e (when M-e is bound to `eek-eval-sexp-eol')."
  (if ee-arg (to anchor)
    (ee-at anchor (ee-once (eevnow-bounded)))))

(defun eevnow-at-file (fname anchor)
"Move temporarily to file FNAME, at anchor ANCHOR, run `eevnow-bounded' there.
This function is similar to `eevnow-at'."
  (if ee-arg (find-anchor fname anchor)
    (ee-at-file fname anchor (ee-once (eevnow-bounded)))))




(provide 'eev-steps)



;; Local Variables:
;; coding:            raw-text-unix
;; ee-anchor-format:  "«%s»"
;; ee-anchor-format:  "defun %s "
;; no-byte-compile:   t
;; End: