1 ;;; pbook.el -- Format a program listing for LaTeX.
3 ;;; More mangling by Paul Khuong on 2007-Jan-31 to typeset
4 ;;; code in a more generic and customisable manner, including
5 ;;; a rough xref (annotates definition sites).
6 ;;; Mangled by Paul Khuong (pvk@pvk.ca) on 2007-Jan-20 to
7 ;;; emit coloured/bold/italic \tt code instead of verbatim
8 ;;; pbook.el,v 1.4 2007/01/20
9 ;;; Written by Luke Gorrie <luke@member.fsf.org> in May of 2004.
10 ;;; $Id: pbook.el,v 1.3 2004/05/17 01:09:01 luke Exp luke $
14 ;;; X Remove FIXMEs, etc from index.
16 ;;; X Replace pbook-latex-escape with pbook-escape-code-substring
17 ;;; Find way to make space work.
19 ;;; o Better xreferencing. Run a first pass to identify all
20 ;;; the toplevel definitions [as fontified], then annotate
21 ;;; the source correctly; either change the face, or use the
22 ;;; escaping mechanism. Changing the face sounds more robust.
23 ;;; This could run as advice over pbook-process-buffer.
24 ;;; The face changing could be an advice on pbook-escape-code,
25 ;;; or, since we ignore face info outside of code, over the
26 ;;; whole buffer, w/ the definition identification.
27 ;;; If want to avoid multiple passes (why?), can accumulate
28 ;;; toplevel defn names while processing them.
30 ;;; X Add a public variable for the current line number (for
31 ;;; property -> latex, especially)
33 ;;; _ Make all/most of the extras togglable.
35 ;;; X Rewrite the code escaping functions to use regexes instead of
36 ;;; searching ourselves.
38 ;;; ? Allow escaped `raw' latex strings in comments
40 ;;; o Improve (think) the interface for customising code output
42 ;;; X Find some way to customise font-lock for paper automatically,
43 ;;; or introduce alists for italic, bold & colour override (per face)?
44 ;;; Currently: converts to yuv, flips the luminance and back to rgb.
48 ;;; Have you ever printed out a program and read it on paper?
50 ;;; It is an interesting exercise to try with one of your own
51 ;;; programs, one that you think is well-written. The first few times
52 ;;; you will probably find that it's torture to try and read in a
53 ;;; straight line. What seemed so nice in Emacs is riddled with
54 ;;; glaring problems on paper.
56 ;;; How a program reads on paper may not be very important in itself,
57 ;;; but there is wonderful upside to this. If you go through the
58 ;;; program with a red pen and fix all the mind-bendingly obvious
59 ;;; problems you see, what happens is that the program greatly
60 ;;; improves -- not just on paper, but also in Emacs!
62 ;;; This is a marvellously effective way to make programs
65 ;;; Let's explore the idea some more!
69 ;;; This program, `pbook', is a tool for making readable programs by
70 ;;; generating LaTeX'ified program listings. Its purpose is to help
71 ;;; you improve your programs by making them read well on paper. It
72 ;;; serves this end by generating pretty-looking PDF output for you to
73 ;;; print out and attack with a red pen, and perhaps use the medium to
74 ;;; trick your mind into seeking the clarity of a technical paper and
75 ;;; bringing your prose-editing skills to bear on your source code.
77 ;;; `pbook' is aware of three things: headings, top-level comments,
78 ;;; and code. Headings become LaTeX sections, and have entries in a
79 ;;; table of contents. Top-level comments become plain text in a nice
80 ;;; variable-width font. Other source code is listed as-is in a
83 ;;; These different elements are distinguished in the source using
84 ;;; maximally unobtrusive markup, which you can see at work in the
85 ;;; `pbook.el' source code.
87 ;;; Read on to see the program and how it works.
91 ;;; (I have successfully tested this program with GNU Emacs versions
92 ;;; 20.7 and 21.3, and with XEmacs version 21.5.)
94 ;;; This is actually not true anymore. I have tested this in GNU Emacs
95 ;;; 22.??? and 21.???. While I don't expect there to be any portability
96 ;;; problem, it has not been tested in XEmacs at all.
98 ;;; For some tiny luxuries and portability help we use the Common Lisp
99 ;;; compatibility library:
104 ;;; A handful of Emacs commands make up the pbook user-interface. The
105 ;;; most fundamental is to render a pbook-formatted Emacs buffer as
108 (defun pbook-buffer ()
109 "Generate LaTeX from the current (pbook-formatted) buffer.
110 The resulting source is displayed in a buffer called *pbook*."
112 (pbook-process-buffer))
114 ;;; A very handy utility is to display a summary of the buffer's
115 ;;; structure and use it to jump to an appropriate section. I've
116 ;;; always enjoyed being able to do this in texinfo-mode. Happily,
117 ;;; pbook gets this for free using the `occur' function, which lists
118 ;;; all lines in the buffer that match some regular expression.
120 (defun pbook-show-structure ()
121 "Display the pbook heading structure of the current buffer."
123 (occur pbook-heading-regexp))
125 ;;; To avoid a lot of mucking about in the shell there is also a
126 ;;; command to generate and display a PDF file. This function is a
127 ;;; quick hack to make experimentation easy.
129 ;;; I should add a function to do the same with dvis, and to output
130 ;;; to non-temporary files.
132 (defun pbook-buffer-view-pdf ()
133 "Generate and display PDF from the current buffer.
134 The intermediate files are created in the standard temporary
137 (save-window-excursion
139 (with-current-buffer "*pbook*"
140 (let ((texfile (pbook-tmpfile "pbook" "tex"))
141 (pdffile (pbook-tmpfile "pbook" "pdf"))
142 (idxfile (pbook-tmpfile "pbook" "idx")))
143 (write-region (point-min) (point-max) texfile)
144 ;; Possibly there is a better way to ensure that LaTeX generates
145 ;; the table of contents correctly than to run it more than
146 ;; once, but I don't know one.
147 (shell-command (format "\
148 cd /tmp; latex %s && \
150 pdflatex %s && acroread %s &"
155 (defun pbook-buffer-view-dvi ()
156 "Generate and display DVI from the current buffer.
157 The intermediate files are created in the standard temporary
160 (save-window-excursion
162 (with-current-buffer "*pbook*"
163 (let ((texfile (pbook-tmpfile "pbook" "tex"))
164 (dvifile (pbook-tmpfile "pbook" "dvi"))
165 (idxfile (pbook-tmpfile "pbook" "idx")))
166 (write-region (point-min) (point-max) texfile)
167 ;; Possibly there is a better way to ensure that LaTeX generates
168 ;; the table of contents correctly than to run it more than
169 ;; once, but I don't know one.
170 (shell-command (format "\
171 cd /tmp; latex %s && \
173 latex %s && xdvi %s &"
178 (defun pbook-buffer-regenerate-dvi ()
180 (save-window-excursion
182 (with-current-buffer "*pbook*"
183 (let ((texfile (pbook-tmpfile "pbook" "tex"))
184 (idxfile (pbook-tmpfile "pbook" "idx")))
185 (write-region (point-min) (point-max) texfile)
186 ;; Possibly there is a better way to ensure that LaTeX generates
187 ;; the table of contents correctly than to run it more than
188 ;; once, but I don't know one.
189 (shell-command (format "\
190 cd /tmp; latex %s && \
197 (defun pbook-tmpfile (name extension)
198 "Return the full path to a temporary file called NAME and with EXTENSION.
199 An appropriate directory is chosen and the PID of Emacs is inserted
200 before the extension."
202 (if (boundp 'temporary-file-directory)
203 temporary-file-directory
204 ;; XEmacs does it this way instead:
206 name (emacs-pid) extension))
208 ;;;# Configurable variables
210 ;;; These are variables that can be customized to affect pbook's
211 ;;; behaviour. The default regular expressions assume Lisp-style
212 ;;; comment characters, but they can be overridden with buffer-local
213 ;;; bindings from hooks for other programming modes. The other
214 ;;; variables that control formatting are best configured with Emacs's
215 ;;; magic "file variables" (see down the very bottom for an example).
217 (defvar pbook-commentary-regexp "^;;;\\($\\|[^#]\\)"
218 "Regular expression matching lines of high-level commentary.")
220 (defvar pbook-heading-regexp "^;;;\\(#+\\)"
221 "Regular expression matching heading lines of chapters/sections/headings.")
223 (defvar pbook-heading-level-subexp 1
224 "The subexpression of `pbook-heading-regexp' whose length indicates nesting.")
226 (defvar pbook-include-toc t
227 "When true include a table of contents.")
229 (defvar pbook-style 'article
230 "Style of output. Either article (small) or book (large).")
232 (defvar pbook-author (user-full-name)
233 "The name to use in the \author LaTeX command.")
235 ;;;## Configuration variables for code formatting
237 (defvar pbook-code-prologue "\
239 \\begin{adjustwidth}{0in}{-1.5in}
242 "Tex string to prepend to code listings")
244 (defvar pbook-code-epilogue "\
249 "Tex string to append to code listings")
251 (defvar pbook-current-line nil
252 "Holds the line number being processed. Note that this
253 is reset for every new section of code. This variable
254 is only accessible while processing code lines, obviously.")
256 (defvar pbook-current-total-lines nil
257 "Holds the total number of lines in the section of
258 code that's being processed.")
260 (defun pbook-around-code-line (line-number total-lines)
261 "returns a list of Tex strings `(prepend append)' to
262 surround the line in a code listing. It may also append
263 any number of entries to put in `pbook-escaping-regexps'.
264 It receives, as its arguments, the line number and the
265 total number of lines in the code segment."
266 (labels ((repeat (string num)
269 (concat string (repeat string (1- num))))))
270 (if (looking-at "^ *$")
272 (let ((str (number-to-string (1+ line-number))))
273 (list (concat "\\hspace{-.4in}{\\small\\texttt{"
274 (repeat "\\ " (max 1 (- 4 (length str))))
278 (if (= line-number (- total-lines 1))
280 "\\nopagebreak[4]\\\\"))))))
282 (defvar pbook-dark-colors '("black")
283 "List of dark colours. Used by the coloring property
284 to detect when to flip luminances.")
286 (defvar pbook-face-latex-properties '()
287 "plist of latex properties for current face
288 (only active while calling functions in `pbook-properties')")
290 (defvar pbook-monochrome t
291 "Force every color to be the specified color (list of rgb components)
292 or t for standard black. nil for normal colors.")
294 (defvar pbook-font-lock-override '(("\\(\\.\\|\\w\\)\\{2,\\}"
295 0 (string-to-syntax "word") ;; was `pbook-identifier'
297 "Appended to `font-lock-syntactic-keywords' while fontifying.")
299 ;;;## configuration for the translation of properties to Latex
301 ;;; The code formatting engine is composed of two parts:
302 ;;; a system of properties that link font-lock faces and syntactic
303 ;;; markup with latex environments, and a string escaping function.
304 ;;; These variables let us easily customise both of these systems.
306 (defvar pbook-current-text-properties nil
307 "text-properties of the current region of text. Maybe be used
308 by the property transformation functions to fine-tune their
311 ;;; The most common customisation will be to change the way faces
312 ;;; are shown on paper. By default, they are translated faithfully:
313 ;;; color, slantedness and boldness are directly translated.
314 ;;; While a value of `nil' means don't care (and defers to other
315 ;;; faces properties or default property values), `:no', by default,
316 ;;; disables the associated property.
318 (defvar pbook-face-override '((font-lock-keyword-face :bold t :index :no)
319 (font-lock-builtin-face :bold t :index :no)
320 (font-lock-function-name-face
323 (font-lock-variable-name-face
326 (font-lock-warning-face :bold t :index :no)
327 (font-lock-comment-face :italic t :tt :no :index :no)
328 (font-lock-doc-face :italic t :tt :no :index :no)
329 (font-lock-constant-face :italic :no :index :no)
330 (font-lock-string-face :index :no)
331 (paren-face :intensity .5)
332 (default :italic :no))
333 "Alist that associates a face with a set of default properties.")
335 (defvar pbook-escaping-regexps '(("<" . "\\\\textless{}")
336 (">" . "\\\\textgreater{}")
337 ("\\\\" . "\\\\textbackslash{}")
338 ("~" . "\\\\textasciitilde{}")
339 ("\\^" . "\\\\textasciicircum{}")
340 ("[#%&$_{}]" . "\\\\\\&")) ;;space added as needed in pbook-latex-escape
341 "alist of regexp -> replacement (passed to re-search-forward and replace-match)
342 A simple way to index FIXMEs would be to add a regex for that in this list.")
344 ;;; `pbook-properties' defines pbook properties: their name,
345 ;;; when they are applied (by default), and how they are translated
347 ;;; It is a list of triples `([name] [default-value] [translater])'.
348 ;;; `[name]' is an unique identifier for the property.
349 ;;; `[default-value]' is an unary function that, given the face,
350 ;;; returns the value to associate with the property (or `nil'
352 ;;; `[translater]' is either an unary function that, given
353 ;;; the property's value, returns a list of a string to prepend
354 ;;; to the formatted region, a string to append to it, and
355 ;;; any number of pairs as in `pbook-escaping-regexps'.
356 ;;; Latex code is spliced outside (for the first property)
357 ;;; in (for the last property).
359 (defvar pbook-properties
364 (eq pbook-monochrome t)
365 (every (lambda (component)
369 (let ((components (mapcar (lambda (component)
370 (if (< component 0.01)
372 (number-to-string component)))
375 (list (format "\\textcolor[rgb]{%s, %s, %s}{"
387 (let* ((default-color (if (or (null pbook-monochrome)
388 (eq pbook-monochrome t))
391 (yuv-default (apply 'pbook-rgb-yuv default-color))
392 (yuv-intensity (list* (- 1 (* intensity (- 1 (car yuv-default))))
394 (rgb-intensity (apply 'pbook-yuv-rgb yuv-intensity)))
396 "\\textcolor[rgb]{%s, %s, %s}{"
404 (looking-at " *[^ ]")
405 '("\\textbf{" "}"))))
407 (:italic face-italic-p
411 (looking-at " *[^ ]")
412 '("\\textit{" "}"))))
417 (when (or (not (looking-at " *[^ ]")) ;; always \tt whitespace.
419 (not (eq prop :no))))
420 '("\\texttt{" "}"))))
426 (:index (lambda (face)
427 (and (eq face 'default)
428 (equal (plist-get pbook-current-text-properties
430 (string-to-syntax "word"))
434 (not (eq index :no)))
435 (let* ((pbook-escaping-regexps (list* (cons "[!@|]" "\"\\&")
436 pbook-escaping-regexps))
437 (word (pbook-latex-escape-string (buffer-string))))
438 (list (format "\\index{%s%s}{" word (ecase index
444 "Complex system. See paragraph above.")
446 (defun pbook-face-color (face)
447 "Given a face, return a triplet of rgb values. Flips the luminance
448 as needed (to adapt dark background colours to a light background)."
449 (let ((dark-bg-p (or (and (boundp 'face-background-mode)
450 (eq face-background-mode 'dark))
451 (member (face-background face)
452 pbook-dark-colors))))
453 (and (face-foreground face)
454 (let ((rgb-specs (mapcar (lambda (n)
456 (color-values (face-foreground face)))))
459 (let ((yuv-specs (apply 'pbook-rgb-yuv rgb-specs)))
460 (pbook-yuv-rgb (* 0.25 (- 1 (first yuv-specs)))
465 (defun pbook-rgb-yuv (r g b)
466 "As http://en.wikipedia.org/wiki/YUV -- Matrix fixed by mjp."
467 (let* ((y (+ (* 0.299 r)
470 (u (+ (* -0.168740 r)
478 (defun pbook-yuv-rgb (y u v)
479 "As http://en.wikipedia.org/wiki/YUV -- Matrix fixed by mjp"
489 (cond ((< x 0) 0.0) ;; Need to clamp values for some reason
495 ;;; Here we have the top level of the program. Setting up, calling the
496 ;;; formatting engine, piecing things together, and putting on the
497 ;;; finishing touches.
499 ;;; The real work is done in a new buffer called *pbook*. First the
500 ;;; source is fontified, then copied into this buffer and from there
501 ;;; it is massaged into shape.
503 ;;; Most of this is mundane, but there is one tricky part: the source
504 ;;; buffer may have buffer-local values for some pbook settings, and
505 ;;; we have to be careful or we'd lose them when switching into the
506 ;;; *pbook* buffer. This is taken care of by moving the correct values
507 ;;; of all the relevant customizable settings into new dynamic
510 (defun pbook-process-buffer ()
511 "Generate pbook output for the current buffer
512 The output is put in the buffer *pbook* and displayed."
514 (let ((font-lock-syntactic-keywords
515 (append font-lock-syntactic-keywords
516 pbook-font-lock-override)))
517 (setq font-lock-fontified nil) ;; pretend buffer isn't fontified
518 (font-lock-default-fontify-buffer) ;; HACK!!! Looks like an internal...
520 (let ((buffer (current-buffer))
521 (beginning (pbook-tex-beginning))
522 (ending (pbook-tex-ending))
523 (text (buffer-string)))
524 (with-current-buffer (get-buffer-create "*pbook*")
526 (pbook-inherit-buffer-locals buffer
527 '(pbook-commentary-regexp
530 pbook-heading-level-subexp
533 pbook-font-lock-override
534 pbook-face-override))
537 ;; Reformat as LaTeX,
539 (pbook-format-buffer)
540 ;; Insert header & footer.
541 (goto-char (point-min))
543 (goto-char (point-max))
545 (display-buffer (current-buffer)))))
547 (defun pbook-inherit-buffer-locals (buffer variables)
548 "Make buffer-local bindings of VARIABLES using the values in BUFFER."
549 (dolist (v variables)
550 (set (make-local-variable v)
551 (with-current-buffer buffer (symbol-value v)))))
553 (defun pbook-preprocess ()
554 "Cleanup the buffer to prepare for formatting."
555 (goto-char (point-min))
556 ;; FIXME: Currently we just zap all pagebreak characters.
558 (while (re-search-forward "\C-l" nil t)
560 (unless (re-search-forward pbook-heading-regexp nil t)
561 (error "File must have at least one heading."))
563 ;; Delete everything before the first heading.
564 (delete-region (point-min) (point)))
566 (defun pbook-tex-beginning ()
567 "Return the beginning prelude for the LaTeX output."
569 \\documentclass[notitlepage,a4paper]{%s}
570 \\usepackage[nohead,nofoot]{geometry}
572 \\usepackage{bold-extra}
573 \\usepackage{chngpage}
575 \\newcommand{\\ii}[1]{{\\it #1}}
576 \\newcommand{\\bb}[1]{{\\bf #1}}
583 (symbol-name pbook-style)
584 (pbook-latex-escape-string (buffer-name))
585 (pbook-latex-escape-string pbook-author)
586 (if pbook-include-toc "\\tableofcontents" "")))
588 (defun pbook-tex-ending ()
589 "Return the ending of the LaTeX output."
595 ;;;# Escaping special characters
597 ;;; We have to escape characters that LaTeX treats specially. This is
598 ;;; done based on `pbook-escaping-regexps', whicn is defined according
599 ;;; to the rules in the `Special Characters' node of the
600 ;;; LaTeX2e info manual. (CHECKME)
602 (defun pbook-latex-escape-string (string &optional space)
605 (pbook-latex-escape (point-min) (point-max) space)
608 (defun pbook-latex-escape (start end &optional space)
609 "LaTeX-escape special characters in the region from START to END."
611 pbook-escaping-regexps)
612 (let* ((pbook-escaping-regexps (if space
613 (append pbook-escaping-regexps
614 (list (cons " " "\\\\\\&")))
615 pbook-escaping-regexps))
616 (scan-regexp (apply 'concat
617 (car (first pbook-escaping-regexps))
618 (mapcan (lambda (entry)
621 (rest pbook-escaping-regexps)))))
624 (narrow-to-region start end)
626 (while (re-search-forward scan-regexp
628 (goto-char (match-beginning 0))
630 (dolist (entry pbook-escaping-regexps)
631 (let ((test (car entry))
632 (replace (cdr entry)))
633 (when (looking-at test)
634 (replace-match replace)
635 (throw 'out nil)))))))))))
637 ;;;# Processing engine
639 ;;; The main loop scans through the source buffer piece by piece and
640 ;;; converts each one to LaTeX as it goes. There are three sorts of
641 ;;; pieces: headings, top-level commentary, and code.
643 ;;; This loop recognises what type of piece is at the point and then
644 ;;; calls the appropriate subroutine. The subroutines are responsible
645 ;;; for determining where their piece finishes and for advancing the
646 ;;; point beyond the region they have formatted.
648 (defun pbook-format-buffer ()
650 (if (looking-at "^\\s *$")
653 (cond ((looking-at pbook-heading-regexp)
655 ((looking-at pbook-commentary-regexp)
656 (pbook-do-commentary))
660 ;;;## Heading formatting
662 ;;; Each heading line is converted to a LaTeX sectioning command. The
663 ;;; heading text is escaped.
665 (defun pbook-do-heading ()
666 ;; NB: `looking-at' sets the Emacs match data (for match-string, etc)
667 (assert (looking-at pbook-heading-regexp))
668 (let ((depth (length (match-string-no-properties pbook-heading-level-subexp))))
669 ;; Strip off the comment characters and whitespace.
671 (when (looking-at "\\s +")
673 (pbook-latex-escape (line-beginning-position) (line-end-position))
674 (wrap-line (format "\\%s{" (pbook-nth-sectioning-command depth))
678 (defun wrap-line (prefix suffix)
679 "Insert PREFIX at the start of the current line and SUFFIX at the end."
681 (goto-char (line-beginning-position))
683 (goto-char (line-end-position))
686 ;;; LaTeX has different sectioning commands for articles and books, so
687 ;;; we have to choose from the right set. These variables define the
688 ;;; sets in order of nesting -- the first element is top-level, etc.
690 (defconst pbook-article-sectioning-commands
691 '("section" "subsection" "subsubsection")
692 "LaTeX commands for sectioning articles.")
694 (defconst pbook-book-sectioning-commands
695 (cons "chapter" pbook-article-sectioning-commands)
696 "LaTeX commands for sectioning books.")
698 (defun pbook-nth-sectioning-command (n)
699 "Return the sectioning command for nesting level N (top-level is 1)."
700 (let ((commands (ecase pbook-style
701 (article pbook-article-sectioning-commands)
702 (book pbook-book-sectioning-commands))))
703 (nth (min (1- n) (1- (length commands))) commands)))
705 ;;;## Commentary formatting
707 ;;; Top-level commentary is stripped of its comment characters and we
708 ;;; escape all characters that LaTeX treats specially.
710 (defun pbook-do-commentary ()
711 "Format one or more lines of commentary into LaTeX."
712 (assert (looking-at pbook-commentary-regexp))
713 (let ((start (point)))
714 ;; Strip off comment characters line-by-line until end of section.
715 (while (or (looking-at pbook-commentary-regexp)
716 (and (looking-at "^\\s *$")
719 (delete-horizontal-space)
722 (pbook-latex-escape start (point))
723 (pbook-pretty-commentary start (point)))))
725 ;;; These functions define a simple Wiki-like markup language for
726 ;;; basic formatting.
728 (defun pbook-pretty-commentary (start end)
729 "Make commentary prettier."
731 (narrow-to-region start end)
732 (goto-char (point-min))
733 (save-excursion (pbook-pretty-tt))
734 (save-excursion (pbook-pretty-doublequotes))))
736 (defun pbook-pretty-tt ()
737 "Format `single quoted' text with a typewriter font."
738 (while (re-search-forward "`\\([^`']*\\)'" nil t)
739 (replace-match "{\\\\tt \\1}" t)))
741 (defun pbook-pretty-doublequotes ()
742 "Format \"double quoted\" text with ``double single quotes''."
743 (while (re-search-forward "\"\\([^\"]*\\)\"" nil t)
744 (replace-match "``\\1''")))
746 ;;;## Source code formatting
748 ;;; Source text is rendered as defined in pbook-properties.
750 (defun pbook-do-code ()
751 (assert (and (not (looking-at pbook-commentary-regexp))
752 (not (looking-at pbook-heading-regexp))))
753 (let ((start (point))
755 (pbook-goto-end-of-code)
758 (narrow-to-region start end)
759 (pbook-convert-tabs-to-spaces start end)
760 ;;delete trailing newlines and spaces
761 (goto-char (point-max))
762 (while (or (equal (char-syntax (char-before)) " ")
765 (pbook-format-code start (point-max)
766 (count-lines start (point-max)))
767 (goto-char (point-min))
768 (insert pbook-code-prologue)
769 (goto-char (point-max))
770 (insert "\n" pbook-code-epilogue "\n"))))
772 (defun pbook-goto-end-of-code ()
773 "Goto the end of the current section of code."
774 (if (re-search-forward (format "\\(%s\\)\\|\\(%s\\)"
776 pbook-commentary-regexp)
779 (goto-char (point-max))))
781 (defun pbook-convert-tabs-to-spaces (start end)
782 "Replace tab characters with spaces."
785 (narrow-to-region start end)
786 (untabify start end))))
789 (defun pbook-format-code (start end num-lines)
790 "Format the section of code. The third argument is
791 the total number of line in the section."
794 (narrow-to-region start end)
795 (goto-char (point-min))
797 (while (< cur-line num-lines)
798 (pbook-format-line (line-beginning-position) (line-end-position)
801 (beginning-of-line 2))))))
803 (defun pbook-get-inherits (face)
804 "Flattens a face's inheritance list, in order."
805 (let ((faces (cond ((eq face 'unspecified) nil)
808 (mapcan (lambda (face)
809 (let ((inherits (face-attribute face :inherit)))
811 (not (eq inherits 'unspecified)))
812 (cons face (pbook-get-inherits inherits))
816 (defun pbook-translate-face-properties (face props)
817 "Updates the list of properties `props' with those
818 associates with `face'. `pbook-face-override' has priority"
819 (setq props (append props
820 (copy-list (cdr (assoc face pbook-face-override)))))
821 (dolist (defn pbook-properties)
822 (let ((prop-name (first defn))
823 (predicate (second defn)))
824 (unless (plist-get props prop-name)
825 (let ((value (funcall predicate face)))
827 (setq props (plist-put props prop-name value)))))))
830 (defun pbook-face-properties (face)
831 "Finds a face's (and those from which it inherits) pbook
832 properties. Earlier faces in the inheritance list (preorder
833 depth-first) have priority."
834 (let ((faces (append (pbook-get-inherits face)
837 (dolist (face faces props)
838 (setq props (pbook-translate-face-properties face props)))))
840 (defun pbook-properties-latex-strings (plist)
841 "Given a plist of pbook properties, finds the latex
842 strings with which to wrap the text that is being formatted,
843 and the additional regexps with which to escape it."
844 (let* ((pbook-face-latex-properties plist) ;;special var
847 (regexps nil) ;;escaping-regexp entry
849 (dolist (property pbook-properties (list (apply 'concat
851 (apply 'concat append)
853 (let* ((name (first property))
854 (transformer (third property))
855 (foundp (plist-member plist name))
856 (prop (plist-get plist name)))
858 (let ((wrap (if (and (listp transformer)
859 (not (eq (first transformer)
864 (funcall transformer prop))))
866 (push (first wrap) prepend)
867 (push (second wrap) append)
868 (setq regexps (append (cddr wrap)
871 (defun pbook-format-line (start end line-number total-lines)
872 "Format a complete line of code, by spans of constant text property.
873 Also wraps it as per `pbook-around-code-line'. Each span is only escaped
874 at the very end to facilitate examination of the buffer."
877 (narrow-to-region start end)
879 (let* ((pbook-current-line line-number)
880 (pbook-current-total-lines total-lines)
881 (substr-beg (point-marker))
882 (substr-end (point-marker))
883 (wrap (pbook-around-code-line line-number total-lines))
884 (pbook-escaping-regexps (append (cddr wrap)
885 pbook-escaping-regexps)))
886 (move-marker substr-end
887 (next-char-property-change (marker-position substr-beg)))
888 (set-marker-insertion-type substr-beg t)
889 (set-marker-insertion-type substr-end t)
890 ;; Main loop: find spans of constant text properties
891 ;; then get the latex trings to wrap around it.
892 (while (not (equal substr-beg substr-end))
893 (goto-char (marker-position substr-beg))
894 (let ((wrap (save-excursion ;;DOCUMENT ME
896 (narrow-to-region (marker-position substr-beg)
897 (marker-position substr-end))
898 (let ((pbook-current-text-properties
899 (text-properties-at (marker-position substr-beg))))
900 (pbook-properties-latex-strings
901 (pbook-face-properties
902 (get-char-property (marker-position substr-beg)
904 (insert (first wrap))
905 (let ((pbook-escaping-regexps (append (third wrap)
906 pbook-escaping-regexps)))
907 (pbook-latex-escape (marker-position substr-beg)
908 (marker-position substr-end)
910 (goto-char (marker-position substr-end))
911 (insert (second wrap))
913 (move-marker substr-beg (marker-position substr-end))
914 (move-marker substr-end
915 (next-char-property-change
916 (marker-position substr-beg)))))
917 (wrap-line (first wrap)
920 ;;;# Prologue and file variables
924 ;;; We use Emacs's magic `file variables' to make sure pbook is
925 ;;; formatted how it should be:
928 ;; pbook-author: "Luke Gorrie, with modifications by Paul Khuong"
930 ;; pbook-style: article
931 ;; pbook-monochrome: t