1 ;;; screencast.el --- demonstrate the capabilities of Emacs
3 ;; Copyright (C) 2009 ESBEN Andreasen <esbenandreasen@gmail.com>
5 ;; Authors: Esben Andreasen <esbenandreasen@gmail.com>
7 ;; Keywords: demo screencast
9 ;; This file is not an official part of Emacs.
11 ;; This program is free software; you can redistribute it and/or modify
12 ;; it under the terms of the GNU General Public License as published by
13 ;; the Free Software Foundation; either version 2, or (at your option)
16 ;; This program is distributed in the hope that it will be useful,
17 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 ;; GNU General Public License for more details.
21 ;; You should have received a copy of the GNU General Public License
22 ;; along with this program; if not, you can either send email to this
23 ;; program's maintainer or write to: The Free Software Foundation,
24 ;; Inc.; 59 Temple Place, Suite 330; Boston, MA 02111-1307, USA.
28 ;; This file allows you to create video-like sessions, which
29 ;; demonstrates the capabilities of Emacs.
33 ;; Install this file to an appropriate directory in your load-path,
34 ;; and add these expressions to your ~/.emacs
36 ;; (auto-load 'screencast "screencast")
38 ;; Try it out by evaluating (screencast-screencast-producer) and (screencast-screencast-user)
40 ;; Your own screencast files should have a (require 'screencast)
44 ;; PRODUCER : creates a screencast
46 ;; USER : sees a screencast
48 ;; producer sections in this document contains variables which the producer
49 ;; should modify as need be, and functions to be called during the creation of a
54 ;; 1.0: core functionality
56 ;; 1.1: PRODUCER ADDITIONS:
57 ;; public variables which contain information about the current screencast
58 ;; ability to change last-command and last-command-char
59 ;; ability to use let and flet, while still outputting command descriptions
60 ;; ability to create blinking sections to move the attention towards those
61 ;; ability to show the region as if transient-mark-mode was on
63 ;; split the screencasts for the producer into a basic and an advanced
64 ;; voice synthesizing of typed text. Requires festival to be installed.
65 ;; global speed control
66 ;; typed strings in screencasts can not contain tabs and newlines
69 (defconst screencast-message-buffer-name "*Screencast Messages*"
70 "The name of the buffer to put messages from the screencast in")
72 (defconst screencast-version 1.1 "The version number of the screencast-mode")
74 (defconst screencast-speed-relation-speech-type 18.0
75 "When this is correctly adjusted, speech and typing should end
76 at the same time. Lower values means faster speech.")
77 ;;;; BEGIN USER VARIABLES
78 (defvar screencast-pause-length 2 "The length of a pause ('p) in the screencast")
80 (defvar screencast-pause-char-length 0.12
81 "The time between each typed character in the function `screencast-insert-with-delay'")
83 (defvar screencast-pause-command-length 3
84 "The time between the announcement of the function call, and the call itself.")
86 (defvar screencast-speech nil "If non-nil, slowly typed strings are read aloud")
88 (defvar screencast-speed 1.0 "How fast the screencast should be. Higher values equals higher speed. This can not be changed _during_ the screencast.")
89 ;;;; BEGIN PRODUCER VARIABLES
90 ;; these variables should be changed as needed by the producer
92 (defvar screencast-dont-print-list '(
99 screencast-producer-insert-with-delay
100 screencast-producer-set-last-command
101 screencast-producer-set-last-char
102 screencast-producer-new-buffer
103 screencast-producer-show-region
104 screencast-producer-blink-regions)
105 "A list of lists of function names which aren't printed as
106 being evaluated in the messages, this includes all producer
107 functions by default")
109 (defvar screencast-producer-blink-time 0.5
110 "The time a blink lasts.")
112 ;; variables which can be read during run-time to obtain information about the
113 ;; current screencast
114 (defvar screencast-producer-nopause nil
115 "Variable to be used for producer functions if they are using
116 pauses, they should deactivate the pause if this variable is non-nil.")
118 (defvar screencast-producer-command-buffer nil
119 "Variable to be used for producer functions if they are using
120 need the current command-buffer.")
122 (defvar screencast-producer-step-number 0
123 "Variable to be used for producer functions if they need to
124 know the current step number.
125 This is a _COPY_ of the value the screencast uses!")
127 (defvar screencast-producer-beginat 0
128 "Variable to be used for producer functions if they need to
129 know where the screencast is supposed to be using pauses at")
130 ;;;; END PRODUCER VARIABLES
134 (defvar screencast-mode-map
135 (let ((map (make-sparse-keymap)))
136 (define-key map (kbd "RET") 'screencast-goto-step)
138 "Keymap for `screencast-mode'."
141 (define-derived-mode screencast-mode nil "screencast"
142 "Major mode for viewing screencasts."
147 ;;;; BEGIN PRODUCER FUNCTIONS
148 (defun screencast-producer-insert-with-delay (string)
149 "Screencast producer function. _i_nserts STRING with a delay between each character.
150 See `screencast-insert-with-delay' for more details."
151 (let ((screencast-speech nil))
152 (screencast-insert-with-delay string screencast-producer-nopause)))
154 (defalias 'i 'screencast-producer-insert-with-delay
155 "Short name for `screencast-producer-insert-with-delay'.
156 This is chosen as it improves readability a lot in the screencast-source.")
158 (defun screencast-producer-set-last-command (f last)
159 "Sets the last-command to LAST before evaluating F.
160 Also prints the info about F, like it would have done normally."
161 (screencast-producer-show-command (car f))
162 (eval-with-last f last)
165 (defun eval-with-last (f last)
167 ;; wtf? that's the only way it works (lines can be permuted!)
168 '(setq last-command last)
169 '(setq this-command last)
172 (defun screencast-producer-set-last-char (char f)
173 "Sets the last-command to CHAR before evaluating F.
174 Also prints the info about F, like it would have done normally."
175 (screencast-producer-show-command (car f))
177 '(setq last-command-char (string-to-char char))
180 (defun screencast-producer-show-command (command)
181 "Shows the COMMAND, and how it can be called in the message-buffer."
182 (pop-to-buffer (get-buffer screencast-message-buffer-name))
183 (screencast-show-command command
184 screencast-producer-step-number
185 screencast-producer-command-buffer)
186 (pop-to-buffer (get-buffer screencast-producer-command-buffer)))
188 (defun screencast-producer-new-buffer (list command-buffer-name)
189 "Screencast producer function. Creates an new screencast with
190 COMMAND-BUFFER-NAME as the command-buffer. The message-buffer
191 remains the same. Once the inner screencast ends, the original
192 command-buffer regains its status.
197 You are responsible for killing the `COMMAND-BUFFER'
198 before the outermost screencast ends, otherwise you'll receive
199 the modified buffer the next time you run the outermost
201 ;; we want to start an 'inner screencast', but the current buffer is the
202 ;; command-buffer, and the expected starting buffer is the
203 ;; screencast-message-buffer
204 (pop-to-buffer (get-buffer screencast-message-buffer-name))
205 (screencast-internal list
206 (get-buffer command-buffer-name)
207 screencast-producer-beginat)
210 (defun screencast-producer-show-region (beg end)
211 "Marks the currently active region as if transient mark mode was on."
212 (unless screencast-producer-nopause
213 (let ((overlay (make-overlay beg end)))
214 (overlay-put overlay 'face (cons beg end))
215 ;; unless there's a LOT of regions, the blinks will be synchronous
216 (run-with-timer screencast-pause-length nil 'delete-overlay overlay)
218 (sit-for screencast-pause-length)
222 (defun screencast-producer-blink-regions (regions)
223 "The REGIONS will blink.
224 A region is a pair: (beg . end)."
225 (unless screencast-producer-nopause
227 (dolist (region regions)
228 (let ((overlay (make-overlay (car region) (cdr region))))
229 (overlay-put overlay 'face 'region)
230 ;; unless there's a LOT of regions, the blinks will be synchronous
231 (run-with-timer screencast-producer-blink-time nil 'delete-overlay overlay))
233 (sit-for (* 2 screencast-producer-blink-time))
236 ;;;; END PRODUCER FUNCTIONS
238 (defun make-region-clickable (beg end action &optional key)
239 "Makes the chosen region clickable, executing chosen action.
240 Default key is [mouse-1]."
241 (let ((map (make-sparse-keymap))
246 (define-key map keyc action)
253 (defun screencast-fontify-step-region ()
254 "Fontifies regions with step-references.
255 To be called immediately after functions which put step-numbers
256 in the message-buffer. Will fontify from the beginning of the
257 line with the step number to the end of the buffer."
259 (goto-char (point-max))
260 (let ((beg (search-backward-regexp "^Step [[:digit:]]+:" (point-min))))
261 (screencast-put-shadow-and-make-clickable beg (point-max))
264 (defun screencast-put-shadow-and-make-clickable (beg end)
265 "The region between BEG and END becomes shadowed and clickable.
266 `screencast-goto-step' is evalled when clicked"
267 (add-text-properties beg (- end 0)
270 'mouse-face 'highlight
271 'help-echo "mouse-1: continue from this step"
273 (make-region-clickable beg (- end 0) 'screencast-goto-step))
275 (defun screencast-get-step ()
276 "Returns the step-number of a step-reference region.
277 If not in step-reference region, returns nil"
280 ;; check if we are at a step-reference region
282 (goto-char (line-end-position))
285 (search-backward-regexp "^Step [[:digit:]]+:" (line-beginning-position) t)
287 (search-backward-regexp "^ Callable with:" (line-beginning-position) t)))
288 ;; get the step number
290 (search-backward-regexp "^Step \\([[:digit:]]+\\):")
291 (let ((beg (match-beginning 1))
294 (buffer-substring-no-properties beg end))))
295 ;; not in step-reference region
298 (defun repeat-string (s n)
299 (apply 'concat (make-list n s)))
301 (defun screencast-make-break (nopause)
302 (screencast-newline-only-once)
306 (screencast-pause-maybe nopause)
307 (screencast-pause-maybe nopause)
310 (defun screencast-pause-maybe (nopause &optional length)
311 "Pauses the program, unless NOPAUSE is non-nil.
312 If length is nil, a default pause LENGTH is used."
316 screencast-pause-length)))
319 (defun n-first (n list)
320 "The n first elements of a list."
321 (loop for x in list repeat n collect x))
323 (defun buffer-recreate (buffer-name)
324 "Kills the buffer with BUFFER-NAME, and recreates it."
325 (let ((buffer (get-buffer buffer-name)))
327 (when (buffer-file-name buffer)
330 (unless (let ((start (substring-no-properties buffer-name 0 1)))
331 (or (string= start " ") (string= start "*")))
333 (kill-buffer buffer-name)))
334 (get-buffer-create buffer-name))
336 (defun screencast-goto-step (&optional arg)
337 "Restarts the screencast at the chosen ARG step. Default is the first step."
339 (let ((step (if (not (= 1 arg))
341 (screencast-get-step)))
342 ;; bug? using (point), standing at point max gives nil values!
343 (list (get-text-property (point-min) 'screencast-list))
344 (name (get-text-property (point-min) 'screencast-command-buffer-name)))
348 (screencast list name
349 -1 ; we just ran the screencast, so version should be no problem
351 (- step 1) ; the command just before!
356 (defun screencast-newline-only-once ()
357 "Inserts a newline at point if, and only if the current line is nonempty."
358 (unless (= (line-beginning-position) (line-end-position))
362 (defun screencast-make-region-clickable (beg end action &optional key)
363 "Makes the chosen region clickable, executing chosen action.
364 Default key is [mouse-1]."
365 (let ((map (make-sparse-keymap))
370 (define-key map keyc action)
377 (defun screencast-show-command (com step command-buffer)
378 "Inserts the STEP number and key-binding for a command, COM."
379 (screencast-newline-only-once)
380 (insert "Step " (number-to-string step) ": `" (symbol-name com) "'")
382 (insert " Callable with: ")
383 (insert (where-is-return com command-buffer))
384 (screencast-fontify-step-region)
388 (defun screencast-line (&optional length)
392 (screencast-newline-only-once)
393 (insert (repeat-string "-" l))
398 (defun screencast-header ()
399 (screencast-newline-only-once)
404 (defun screencast-speech-start (string nopause)
405 "Starts the speech-synthesizer with STRING, unless NOPAUSE is nonnil.
406 Also requires `screencast-speech' to be non-nil.
407 The speech speed depends on the typing speed (`screencast-speed-relation-speech-type')."
408 (when (and (not nopause) screencast-speech)
409 (let* ((duration (concat
410 "-b \"(Parameter.set 'Duration_Stretch "
411 (number-to-string (* screencast-pause-char-length
412 screencast-speed-relation-speech-type)) ")\""))
413 (tosay (replace-regexp-in-string "'" "'\"'\"'" string))
414 (say (concat "-b '(SayText \"" tosay "\")'"))
416 (save-window-excursion
418 (concat "festival " duration " " say "&"))
421 (defun screencast-speech-wait-for (nopause)
422 "Blocks until the speech synthesizer is done speaking."
423 (when (and (not nopause) screencast-speech)
424 (shell-command "while [ `pgrep festival` ] ; do sleep 0.1; done;")
425 (sit-for 0.1)) ; needed
428 (defun screencast-insert-with-delay (string &optional nopause)
429 "Inserts STRING with a delay between each character.
430 If NOPAUSE is non-nil, the delay will be 0.
432 The pause between each character is given by `screencast-pause-char-length'."
433 (let ((string (screencast-strip-newlines-and-normalize-whitespace string)))
434 (screencast-speech-start string nopause)
435 (let ((l (string-to-list string)))
438 ;; simple filling. If the char position equals fill-column. The
439 ;; whole word is moved to the next line.
440 (when (and (= (- (line-end-position) (line-beginning-position)) fill-column))
441 (search-backward " ")
442 (insert "\n ") ; two space indentation as the previous space is moved too
445 (screencast-pause-maybe nopause screencast-pause-char-length)))
446 (screencast-speech-wait-for nopause)
450 (defun screencast-strip-newlines-and-normalize-whitespace (string)
451 "Replaces all newlines and tabs in STRING by a single
452 whitespace, also collapses multiple whitespaces."
453 (replace-regexp-in-string "[ ]+" " " (replace-regexp-in-string "\n" " " string)))
455 (defalias 'screencast 'screencast-producer-screencast "Renaming for simplicity")
457 (defun screencast-producer-screencast (list command-buffer-name
458 version &optional beginat init)
459 "Prints and evaluates a list, LIST, of strings and functions in a tempo humans can follow.
460 The strings in LIST is printed to the screencast-message-buffer.
461 Functions are evaluated in the buffer named COMMAND-BUFFER-NAME.
462 VERSION is the version of screencast-mode the screencast is
463 written for, older versions of screencast-mode might not support
464 everything in newer screencasts.
465 The first BEGINAT elements of the list will be done without
467 INIT is a list of functions to be evaluated in the message-buffer
468 prior to the first message"
469 (when (> version screencast-version)
471 (concat "The version of the screencast (" (number-to-string
472 version) ") is newer than the version of the screencast-mode
473 itself (" (number-to-string screencast-version) "). You might still be able
474 to run the screencast successfully though, just change the
475 screencasts version number to try it out.")))
481 (screencast-pause-length (/ screencast-pause-length
483 (screencast-pause-char-length (/
484 screencast-pause-char-length
486 (screencast-pause-command-length (/
487 screencast-pause-command-length
490 (message-buffer (buffer-recreate screencast-message-buffer-name))
491 (command-buffer (if (string= command-buffer-name
492 screencast-message-buffer-name)
494 (buffer-recreate command-buffer-name)))
496 (screencast-step-number 0)
500 (delete-other-windows)
501 (split-window-horizontally)
502 (switch-to-buffer message-buffer)
503 (pop-to-buffer message-buffer)
504 (display-buffer command-buffer)
507 ;; evaluate all the functions of init
512 (screencast-internal list command-buffer beginat)
513 ;; save the arguments in the buffer
514 (add-text-properties (point-min) (point-max)
515 (list 'screencast-list list
516 'screencast-command-buffer-name command-buffer-name))
521 (defun screencast-internal (list command-buffer beginat)
522 "The internal version of screencast, refer to the documentation string
524 ;; producer variables
525 (setq screencast-producer-command-buffer command-buffer)
526 (setq screencast-producer-beginat beginat)
527 ;; make sure we are visiting the file in case it is needed (e.g. compile!)
529 (set-buffer command-buffer)
530 (unless (buffer-file-name)
531 (set-visited-file-name (buffer-name))
533 ;; for each element in the list
536 (if (>= screencast-step-number beginat)
539 ;; producer variables
540 (setq screencast-producer-nopause nopause)
541 (setq screencast-producer-step-number screencast-step-number)
547 (screencast-newline-only-once)
548 (insert "Step " (number-to-string screencast-step-number) ":")
549 (screencast-fontify-step-region)
556 (screencast-pause-maybe nopause))
558 (screencast-make-break nopause)
561 (error (concat "Screencast-internal encountered an error: Unknown symbol: " (symbol-name c)))))
566 (unless (member (car c) screencast-dont-print-list) ; these need no print
567 (screencast-show-command (car c) screencast-step-number command-buffer)
570 (screencast-pause-maybe nopause screencast-pause-command-length) ; pause
572 (if (member (car c) '(let flet))
574 ;; we want the environment - but also to print the commands!
575 (eval (list (car c) ; the members above
576 (cadr c) ;the lets of flets
577 '(screencast-internal (cddr c) command-buffer beginat))) ;the rest
581 ;; save excursion style which allows for inner screencasts
582 (pop-to-buffer command-buffer)
584 (pop-to-buffer screencast-message-buffer-name)))
585 (pop-to-buffer screencast-message-buffer-name) ; needed to regain real focus!
588 ;; it's a string - instert it.
589 (screencast-insert-with-delay c nopause))
591 (error (concat "I don't know what to do with element:" c)))
593 (setq screencast-step-number (+ 1 screencast-step-number)) ; inc the step number
598 (defun where-is-return (definition buffer)
599 "A modification of where-is, which returns the message-string instead of printing it.
600 Also skips the removes name from the output.
601 BUFFER is the buffer to call where-is in."
604 (let ((func (indirect-function definition))
607 ;; In DEFS, find all symbols that are aliases for DEFINITION.
608 (mapatoms (lambda (symbol)
609 (and (fboundp symbol)
610 (not (eq symbol definition))
611 (eq func (condition-case ()
612 (indirect-function symbol)
614 (push symbol defs))))
615 ;; Look at all the symbols--first DEFINITION,
617 (dolist (symbol (cons definition defs))
618 (let* ((remapped (command-remapping symbol))
619 (keys (where-is-internal
620 symbol overriding-local-map nil nil remapped))
621 (keys (mapconcat 'key-description keys ", "))
625 (if (> (length keys) 0)
627 (format "%s (%s) (remapped from %s)"
628 keys remapped symbol)
630 (format "M-x %s RET" symbol))
631 (if (> (length keys) 0)
633 (format "%s is remapped to %s which is on %s"
634 symbol remapped keys)
635 (format "%s is on %s" symbol keys))
636 ;; If this is the command the user asked about,
637 ;; and it is not on any key, say so.
638 ;; For other symbols, its aliases, say nothing
639 ;; about them unless they are on keys.
640 (if (eq symbol definition)
641 (format "%s is not on any key" symbol)))))
643 (unless (eq symbol definition)
644 (setq return-string (concat return-string ";\n its alias "))) ;
645 (setq return-string (concat return-string string)))))
649 ;;;; BEGIN DOCUMENTATION
650 (defconst screencast-screencast-text-producer
652 "Hello, this is the screencast for creating your own
654 "If you create a list (first argument) of strings, each
655 string will be typed to the message buffer (this buffer), at
656 a human-readable pace." n
657 "If you put a 'p in the list, a pause will be inserted. " p
660 "(The above line was inserted instantly with the symbol 'l)"
662 "(Blank lines can be inserted using the 'n symbol, newlines
663 in strings are removed)" n n n
664 "All of the above is combined in the symbol 'b, which creates
665 a break in the screencast. This could be used between two
666 different sections for instance." b
667 "You can also put functions in the list, these will be
668 evaluated in the command-buffer (second argument)." n
669 "The function is written as a list, with the function name
670 first, and the arguments after that, e.g. '(backward-char
672 "Each time a function is evaluated, a message is displayed in
673 the message buffer, using the where-is function." n
674 "In addition to this a step-number is displayed, this
675 step-number corresponds to the functions position in the
677 "Let's try out some functions:" n
678 "((insert \"THIS IS AN INSERTION\n\") will be evaluated)" p
679 (insert "THIS IS AN INSERTION\n")
680 "You can call the special function `screencast-producer-insert-with-delay', aliased to `i' to insert with delay in the command-buffer."n
682 "this is also an insertion, but it is done at typing speed")
683 "Hmm.." p p "let's delete the line we just typed in the
684 command buffer [[(kill-whole-line 1)]]"
686 "Notice the keybindings which are displayed." b
687 "The fourth (optional) argument given to the screencast
688 function is the step-number to start using pauses, and output
689 to the message buffer at, e.g. it is a fast-forward. Which is
690 _very_ nice when producing a screencast." n
691 "These step-numbers can also be printed separately in the
692 message-buffer using the 's symbol in the list." n s n
694 "Once you have finished a screencast and want it published,
695 you can record it as a video (.ogv) using
696 `screencast-record'."n
697 "As a part of the recording - the
698 font-size (`screencast-record-font') is changed, as well as
699 the fill-column variable (`screencast-record-fill-column')
700 for improved readability on a video."n
701 "As a consequence, you should _never_ use fill-paragraph and
702 the like, to get a nicely formatted source-file."n
703 "But the Emacs community will benefit the most if you publish
704 the screencast file itself - so please do!"n
705 "You can publish it at
706 http://www.emacswiki.org/emacs/ScreencastSources" b
707 "This screencast should cover the basic options for creating
708 a screencast, and can be seen in the constant
709 `screencast-screencast-text-producer'."n
710 "A screencast covering the more advanced functions of
711 screencast is available in the function
712 `screencast-screencast-producer-advanced'." b
713 "Happy screencasting!" )
714 "The text the screencast-screencast-producer is based upon")
716 (defconst screencast-screencast-text-user '(
717 "Hello, welcome to the screencast for viewing screencasts in
719 "Screencasts are like movies, they type some explanatory
720 text (like this), and executes functions in order to show you
721 the capabilities of different tools in Emacs."n
722 "Once a screencast has finished, you can move the cursor to
723 an executed function and press RET or MOUSE-1 to review the
724 screencast from that step."n
725 "Alternatively you can use the numeric prefix argument to
726 pinpoint the step to begin at."n
727 "If no prefix argument is given, and point isn't at an
728 executed function, the screencast is restarted from the first
731 (defconst screencast-screencast-text-producer-advanced
733 "This screencast covers the advanced functions of screencast-mode."n
734 "Please read the documentation for the functions as well."n
735 "Regarding the functions and variables in this file:"n
736 "You, as a producer, are supposed to be using the functions starting with `screencast-producer-' (and `screencast' itself ofcourse), they are tailored for ease of use. The others are for internal use - and there's no guarantee they are stable throughout versions."
738 "It is possible to use multiple command-buffers:"
739 (screencast-producer-new-buffer
740 '((i "I'm a new command-buffer"))
741 "new-command-buffer")
742 (progn (kill-buffer "new-command-buffer"))
743 "It is done via the function `screencast-producer-new-buffer' which takes a list and a buffer - almost like the screencast function itself. "
745 "If you don't want to document everything you do, for instance moving the cursor, you can put the functions you want to \"hide\" inside a `progn'."
747 "If you need temporary variables or functions (for instance when you need to override a function which uses the mini-buffer), you can just put in a `let' or `flet'"
749 "If you need to modify the last-command-char (for self-insert-commands) or the last-command (for continued killing) there's also support for that:"n
750 "Use `screencast-producer-set-last-char' or `screencast-producer-set-last-command'"
751 "The text the screencast-screencast-producer-advanced is based upon"))
753 (defun screencast-screencast-producer-advanced(&optional arg)
754 "Displays the screencast for creating advanced screencasts."
759 screencast-screencast-text-producer-advanced "screencast-screencast-producer" 1.1 ()))
761 (defun screencast-screencast-producer(&optional arg)
762 "Displays the screencast for creating screencasts."
767 screencast-screencast-text-producer "screencast-screencast-producer" 1 ()))
769 (defun screencast-screencast-user(&optional arg)
770 "Displays the screencast for using screencasts."
775 screencast-screencast-text-user "screencast-screencast-user" 1 ()))
776 ;;;; END DOCUMENTATION
777 (provide 'screencast)