changeset 190:9328263cb84e

fountain-mode: updat to version 2.6.2
author Jordi Gutiérrez Hermoso <jordigh@octave.org>
date Sun, 05 May 2019 22:55:53 -0400
parents 4bb68b30325b
children 93ed4c92f1bc
files elpa/fountain-mode-2.5.3/fountain-mode-autoloads.el elpa/fountain-mode-2.5.3/fountain-mode-pkg.el elpa/fountain-mode-2.5.3/fountain-mode.el elpa/fountain-mode-2.6.2.signed elpa/fountain-mode-2.6.2/fountain-mode-autoloads.el elpa/fountain-mode-2.6.2/fountain-mode-pkg.el elpa/fountain-mode-2.6.2/fountain-mode.el
diffstat 4 files changed, 1817 insertions(+), 1825 deletions(-) [+]
line wrap: on
line diff
new file mode 100644
--- /dev/null
+++ b/elpa/fountain-mode-2.6.2.signed
@@ -0,0 +1,1 @@
+Good signature from 474F05837FBDEF9B GNU ELPA Signing Agent <elpasign@elpa.gnu.org> (trust undefined) created at 2019-04-24T19:09:26-0400 using DSA
\ No newline at end of file
rename from elpa/fountain-mode-2.5.3/fountain-mode-autoloads.el
rename to elpa/fountain-mode-2.6.2/fountain-mode-autoloads.el
--- a/elpa/fountain-mode-2.5.3/fountain-mode-autoloads.el
+++ b/elpa/fountain-mode-2.6.2/fountain-mode-autoloads.el
@@ -3,8 +3,8 @@
 ;;; Code:
 (add-to-list 'load-path (directory-file-name (or (file-name-directory #$) (car load-path))))
 
-;;;### (autoloads nil "fountain-mode" "fountain-mode.el" (23362 31369
-;;;;;;  7313 833000))
+;;;### (autoloads nil "fountain-mode" "fountain-mode.el" (23759 41327
+;;;;;;  888428 795000))
 ;;; Generated autoloads from fountain-mode.el
 
 (add-to-list 'auto-mode-alist '("\\.fountain\\'" . fountain-mode))
rename from elpa/fountain-mode-2.5.3/fountain-mode-pkg.el
rename to elpa/fountain-mode-2.6.2/fountain-mode-pkg.el
--- a/elpa/fountain-mode-2.5.3/fountain-mode-pkg.el
+++ b/elpa/fountain-mode-2.6.2/fountain-mode-pkg.el
@@ -1,2 +1,2 @@
 ;;; -*- no-byte-compile: t -*-
-(define-package "fountain-mode" "2.5.3" "Major mode for screenwriting in Fountain markup" '((emacs "24.5")))
+(define-package "fountain-mode" "2.6.2" "Major mode for screenwriting in Fountain markup" '((emacs "24.5")) :url "http://elpa.gnu.org/packages/fountain-mode.html" :keywords '("wp" "text"))
rename from elpa/fountain-mode-2.5.3/fountain-mode.el
rename to elpa/fountain-mode-2.6.2/fountain-mode.el
--- a/elpa/fountain-mode-2.5.3/fountain-mode.el
+++ b/elpa/fountain-mode-2.6.2/fountain-mode.el
@@ -1,15 +1,11 @@
 ;;; fountain-mode.el --- Major mode for screenwriting in Fountain markup -*- lexical-binding: t; -*-
 
-;; Copyright (c) 2014-2018 Paul Rankin
+;; Copyright (c) 2014-2019  Free Software Foundation, Inc.
 
 ;; Author: Paul Rankin <hello@paulwrankin.com>
-;; Keywords: wp
-;; Package-Version: 2.5.3
-;; Version: 2.5.3
+;; Keywords: wp, text
+;; Version: 2.6.2
 ;; Package-Requires: ((emacs "24.5"))
-;; URL: https://github.com/rnkn/fountain-mode
-
-;; This file is not part of GNU Emacs.
 
 ;; This program is free software; you can redistribute it and/or modify
 ;; it under the terms of the GNU General Public License as published by
@@ -29,9 +25,9 @@
 ;; Fountain Mode
 ;; =============
 
-;; Fountain Mode is a complete screenwriting environment for GNU Emacs
-;; using the Fountain markup format. For more information on the Fountain markup
-;; format, visit <http://fountain.io>.
+;; Fountain Mode is a screenwriting environment for GNU Emacs using the Fountain
+;; markup format. For more information on the Fountain markup format, visit
+;; <http://fountain.io>.
 
 ;; Features
 ;; --------
@@ -80,68 +76,29 @@
 ;; ------------
 
 ;; - Emacs 24.5
-;; - LaTeX packages for PDF export: `geometry` `fontspec` `titling` `fancyhdr`
-;;   `marginnote` `ulem` `xstring` `oberdiek`
+;; - LaTeX packages for PDF export: geometry fontspec titling fancyhdr
+;;   marginnote ulem xstring oberdiek
 
 ;; Installation
 ;; ------------
 
-;; For users on OS X with no experience with Emacs, see the
-;; [Absolute Beginner's Guide (macOS)][guide].
-
-;; The latest stable release of Fountain Mode is available via
-;; [MELPA-stable](http://stable.melpa.org/#/fountain-mode).
-
-;; Alternately, download the [latest release], move the files into your
-;; `load-path` and add the following line to your `.emacs` or `init.el` file:
-
-;;     (require 'fountain-mode)
-
-;; If you prefer the latest but perhaps unstable version, install via
-;; [MELPA], or clone the repository into your `load-path` and require as
-;; above:
-
-;;     git clone https://github.com/rnkn/fountain-mode.git
-
-;; Users of Debian ≥10 or Ubuntu ≥18.04 can install Fountain Mode with the following command:
-
-;;     sudo apt install elpa-fountain-mode
-
-;; [guide]: https://github.com/rnkn/fountain-mode/wiki/Absolute-Beginner's-Guide-(macOS) "Absolute Beginner's Guide (macOS)"
-;; [melpa]: https://melpa.org/#/fountain-mode "MELPA"
-;; [melpa-stable]: https://stable.melpa.org/#/fountain-mode "MELPA-stable"
-;; [latest release]: https://github.com/rnkn/fountain-mode/releases/latest "Fountain Mode latest release"
-
-;; Bugs and Feature Requests
-;; -------------------------
-
-;; Please raise an issue on [Issues](https://github.com/rnkn/fountain-mode/issues).
-
-;; - Emacs versions prior to 26 have a bug with `visual-line-mode` that produces erratic
-;;   navigation behavior when displaying very long lines. More information here:
-;;   <https://debbugs.gnu.org/23879>
-
-;; Roadmap
-;; -------
-
-;; See [Roadmap](https://github.com/rnkn/fountain-mode/projects/2).
-
-;; History
-;; -------
-
-;; See [Releases](https://github.com/rnkn/fountain-mode/releases).
-
-;; Tips
-;; ----
-
-;; Ethereum address 0x209C60afd8aF6c61ac4Dbe340d81D4f789DF64D3
-;; Bitcoin Cash address 19gUvL8YUzDKr5GyiHpYeF31BfQm87xM9L
+;; Fountain Mode is now part of GNU ELPA and can be installed with `M-x
+;; package-install RET fountain-mode RET`.
+
+;; Reporting Bugs
+;; --------------
+
+;; To report bugs, please use `M-x report-emacs-bug RET` or send an email to
+;; <bug-gnu-emacs@gnu.org>
 
 
 ;;; Code:
 
+(eval-when-compile (require 'subr-x))
+(eval-when-compile (require 'cl-lib))
+
 (defconst fountain-version
-  "2.5.3")
+  "2.6.1")
 
 (defun fountain-version ()
   "Return `fountain-mode' version."
@@ -151,8 +108,7 @@
 (defgroup fountain ()
   "Major mode for screenwriting in Fountain markup."
   :prefix "fountain-"
-  :group 'wp
-  :link '(url-link "https://github.com/rnkn/fountain-mode"))
+  :group 'text)
 
 
 ;;; Obsolete Warnings
@@ -160,21 +116,15 @@
 (define-obsolete-variable-alias 'fountain-align-centered
   'fountain-align-center "1.1.0")
 
-(define-obsolete-variable-alias 'fountain-export-title-page-template
-  'fountain-export-title-page-title-template "1.1.0")
-
 (define-obsolete-variable-alias 'fountain-hide-escapes
   'fountain-hide-syntax-chars "1.3.0")
 
 (make-obsolete-variable 'fountain-export-inline-style
-                        "use inline style instead." "2.1.0")
+                        "Use inline style instead." "2.1.0")
 
 (define-obsolete-variable-alias 'fountain-export-style-template
   'fountain-export-html-stylesheet "2.4.0")
 
-(define-obsolete-function-alias 'fountain-toggle-hide-escapes
-  'fountain-toggle-hide-syntax-chars "1.3.0")
-
 (define-obsolete-face-alias 'fountain-centered
   'fountain-center "1.1.0")
 
@@ -209,19 +159,19 @@
   'fountain-section-heading "1.4.1")
 
 (make-obsolete-variable 'fountain-export-title-page-left-template
-                        "edit individual export templates instead." "2.4.0")
+                        "Edit individual export templates instead." "2.4.0")
 
 (make-obsolete-variable 'fountain-export-title-page-right-template
-                        "edit individual export templates instead." "2.4.0")
+                        "Edit individual export templates instead." "2.4.0")
 
 (make-obsolete 'fountain-export-buffer-to-pdf-via-html
-               'fountain-export-buffer-to-tex "2.4.0")
+               'fountain-export-buffer-to-latex "2.5.0")
 
 (make-obsolete-variable 'fountain-export-pdf-via-html-command
                         'fountain-export-shell-command "2.0.0")
 
 (make-obsolete-variable 'fountain-uuid-func
-                        "use a third-party package instead." "2.0.0")
+                        "Use a third-party package instead." "2.0.0")
 
 (make-obsolete-variable 'fountain-export-bold-scene-headings
                         'fountain-export-scene-heading-format "2.0.0")
@@ -233,22 +183,22 @@
                         'fountain-export-scene-heading-format "2.0.0")
 
 (make-obsolete-variable 'fountain-export-bold-title
-                        "edit individual export templates instead." "2.4.0")
+                        "Edit individual export templates instead." "2.4.0")
 
 (make-obsolete-variable 'fountain-export-underline-title
-                        "edit individual export templates instead." "2.4.0")
+                        "Edit individual export templates instead." "2.4.0")
 
 (make-obsolete-variable 'fountain-export-upcase-title
-                        "edit individual export templates instead." "2.4.0")
+                        "Edit individual export templates instead." "2.4.0")
 
 (make-obsolete-variable 'fountain-export-html-head-template
                         'fountain-export-html-template "2.4.0")
 
 (make-obsolete-variable 'fountain-export-html-use-inline-style
-                        "use inline style instead." "2.1.0")
+                        "Use inline style instead." "2.1.0")
 
 (make-obsolete-variable 'fountain-additional-template-replace-functions
-                        "see `fountain-export-formats'." "2.4.0")
+                        "See `fountain-export-formats'." "2.4.0")
 
 (make-obsolete 'fountain-insert-metadata
                'auto-insert "2.1.2")
@@ -263,19 +213,25 @@
   'fountain-time-format "2.1.2")
 
 (make-obsolete-variable 'fountain-export-templates
-                        "use individual export templates instead." "2.1.4")
+                        "Use individual export templates instead." "2.1.4")
+
+(define-obsolete-variable-alias 'fountain-align-scene-number
+  'fountain-display-scene-numbers-in-margin "2.3.0")
 
 (make-obsolete-variable 'fountain-export-format-replace-alist
-                        "see `fountain-export-formats'." "2.4.0")
+                        "See `fountain-export-formats'." "2.4.0")
 
 (make-obsolete-variable 'fountain-export-title-format
-                        "edit individual export templates instead." "2.4.0")
+                        "Edit individual export templates instead." "2.4.0")
 
 (define-obsolete-variable-alias 'fountain-trans-list
   'fountain-trans-suffix-list "2.2.2")
 
 (make-obsolete-variable 'fountain-switch-comment-syntax
-                        "use the standard comment syntax instead." "2.4.0")
+                        "Use the standard comment syntax instead." "2.4.0")
+
+(define-obsolete-variable-alias 'fountain-export-include-elements-alist
+  'fountain-export-include-elements "2.4.0")
 
 (define-obsolete-variable-alias 'fountain-export-standalone
   'fountain-export-make-standalone "2.4.0")
@@ -283,23 +239,45 @@
 (define-obsolete-variable-alias 'fountain-export-buffer-name
   'fountain-export-tmp-buffer-name "2.4.0")
 
-(define-obsolete-variable-alias 'fountain-endnotes-buffer-name
-  'fountain-endnotes-buffer "2.5.1")
+(make-obsolete-variable 'fountain-outline-startup-level
+                        'fountain-outline-custom-level "2.5.4")
+
+(make-obsolete-variable 'fountain-endnotes-buffer
+                        "Use a third-party package instead" "2.6.0")
 
 (make-obsolete-variable 'fountain-endnotes-window-side
-                        'fountain-endnotes-display-alist "2.5.1")
+                        "Use a third-party package instead" "2.6.0")
 
 (make-obsolete-variable 'fountain-endnotes-window-size
-                        'fountain-endnotes-display-alist "2.5.1")
+                        "Use a third-party package instead" "2.6.0")
 
 
 ;;; Customization
 
+(defun fountain--set-and-refresh-all-font-lock (symbol value)
+  (set-default symbol value)
+  (dolist (buffer (buffer-list))
+    (with-current-buffer buffer
+      (when (derived-mode-p 'fountain-mode)
+        (font-lock-refresh-defaults)))))
+
 (defcustom fountain-mode-hook
-  '(turn-on-visual-line-mode)
+  '(turn-on-visual-line-mode fountain-outline-hide-custom-level)
   "Mode hook for `fountain-mode', run after the mode is turned on."
   :type 'hook
-  :group 'fountain)
+  :options '(turn-on-visual-line-mode
+             fountain-outline-hide-custom-level
+             fountain-completion-update
+             turn-on-flyspell))
+
+(defcustom fountain-script-format "screenplay"
+  "Default script format for editing and exporting.
+
+Can be overridden in metadata with, e.g.
+
+    format: teleplay"
+  :type 'string
+  :safe 'string)
 
 (defcustom fountain-add-continued-dialog
   t
@@ -311,7 +289,7 @@
 When nil, remove `fountain-continued-dialog-string' with
 `fountain-continued-dialog-refresh'."
   :type 'boolean
-  :group 'fountain)
+  :safe 'booleanp)
 
 (defcustom fountain-continued-dialog-string
   "(CONT'D)"
@@ -326,44 +304,44 @@
 to nil and run `fountain-continued-dialog-refresh', then make the
 changes desired."
   :type 'string
-  :group 'fountain)
+  :safe 'stringp)
 
 (defcustom fountain-hide-emphasis-delim
   nil
   "If non-nil, make emphasis delimiters invisible."
   :type 'boolean
-  :group 'fountain
+  :safe 'booleanp
   :set (lambda (symbol value)
          (set-default symbol value)
          (dolist (buffer (buffer-list))
            (with-current-buffer buffer
-             (when (eq major-mode 'fountain-mode)
-                   (if fountain-hide-emphasis-delim
-                       (add-to-invisibility-spec 'fountain-emphasis-delim)
-                     (remove-from-invisibility-spec 'fountain-emphasis-delim))
-                   (font-lock-refresh-defaults))))))
+             (when (derived-mode-p 'fountain-mode)
+               (if fountain-hide-emphasis-delim
+                   (add-to-invisibility-spec 'fountain-emphasis-delim)
+                 (remove-from-invisibility-spec 'fountain-emphasis-delim))
+               (font-lock-refresh-defaults))))))
 
 (defcustom fountain-hide-syntax-chars
   nil
   "If non-nil, make syntax characters invisible."
   :type 'boolean
-  :group 'fountain
+  :safe 'booleanp
   :set (lambda (symbol value)
          (set-default symbol value)
          (dolist (buffer (buffer-list))
            (with-current-buffer buffer
-             (when (eq major-mode 'fountain-mode)
-                   (if fountain-hide-syntax-chars
-                       (add-to-invisibility-spec 'fountain-syntax-chars)
-                     (remove-from-invisibility-spec 'fountain-syntax-chars))
-                   (font-lock-refresh-defaults))))))
+             (when (derived-mode-p 'fountain-mode)
+               (if fountain-hide-syntax-chars
+                   (add-to-invisibility-spec 'fountain-syntax-chars)
+                 (remove-from-invisibility-spec 'fountain-syntax-chars))
+               (font-lock-refresh-defaults))))))
 
 (defcustom fountain-time-format
   "%F"
   "Format of date and time used when inserting `{{time}}'.
 See `format-time-string'."
   :type 'string
-  :group 'fountain)
+  :safe 'stringp)
 
 (defcustom fountain-note-template
   " {{time}} - {{fullname}}: "
@@ -381,12 +359,7 @@
 
     [[ 2017-12-31 - Alan Smithee: ]]"
   :type 'string
-  :group 'fountain)
-;; FIXME:
-;; {{title}} from metadata
-;; {{author}} from metadata
-;; {{username}} `user-full-name'
-;; {{KEY}} arbitrary metadata
+  :safe 'stringp)
 
 
 ;;; Aligning
@@ -412,13 +385,8 @@
   "If non-nil, elements will be displayed auto-aligned.
 This option does not affect file contents."
   :type 'boolean
-  :group 'fountain-align
-  :set (lambda (symbol value)
-         (set-default symbol value)
-         (dolist (buffer (buffer-list))
-           (with-current-buffer buffer
-             (when (eq major-mode 'fountain-mode)
-                   (font-lock-refresh-defaults))))))
+  :safe 'booleanp
+  :set #'fountain--set-and-refresh-all-font-lock)
 
 (defcustom fountain-align-section-heading
   '(("screenplay" 0)
@@ -429,7 +397,7 @@
 This option does not affect file contents."
   :type '(choice integer
                  (repeat (group (string :tag "Format") integer)))
-  :group 'fountain-align)
+  :set #'fountain--set-and-refresh-all-font-lock)
 
 (defcustom fountain-align-scene-heading
   '(("screenplay" 0)
@@ -440,7 +408,7 @@
 This option does not affect file contents."
   :type '(choice integer
                  (repeat (group (string :tag "Format") integer)))
-  :group 'fountain-align)
+  :set #'fountain--set-and-refresh-all-font-lock)
 
 (defcustom fountain-align-synopsis
   '(("screenplay" 0)
@@ -451,7 +419,7 @@
 This option does not affect file contents."
   :type '(choice integer
                  (repeat (group (string :tag "Format") integer)))
-  :group 'fountain-align)
+  :set #'fountain--set-and-refresh-all-font-lock)
 
 (defcustom fountain-align-action
   '(("screenplay" 0)
@@ -462,7 +430,7 @@
 This option does not affect file contents."
   :type '(choice integer
                  (repeat (group (string :tag "Format") integer)))
-  :group 'fountain-align)
+  :set #'fountain--set-and-refresh-all-font-lock)
 
 (defcustom fountain-align-character
   '(("screenplay" 20)
@@ -473,7 +441,7 @@
 This option does not affect file contents."
   :type '(choice integer
                  (repeat (group (string :tag "Format") integer)))
-  :group 'fountain-align)
+  :set #'fountain--set-and-refresh-all-font-lock)
 
 (defcustom fountain-align-dialog
   '(("screenplay" 10)
@@ -484,7 +452,7 @@
 This option does not affect file contents."
   :type '(choice integer
                  (repeat (group (string :tag "Format") integer)))
-  :group 'fountain-align)
+  :set #'fountain--set-and-refresh-all-font-lock)
 
 (defcustom fountain-align-paren
   '(("screenplay" 15)
@@ -495,7 +463,7 @@
 This option does not affect file contents."
   :type '(choice integer
                  (repeat (group (string :tag "Format") integer)))
-  :group 'fountain-align)
+  :set #'fountain--set-and-refresh-all-font-lock)
 
 (defcustom fountain-align-trans
   '(("screenplay" 45)
@@ -506,7 +474,7 @@
 This option does not affect file contents."
   :type '(choice integer
                  (repeat (group (string :tag "Format") integer)))
-  :group 'fountain-align)
+  :set #'fountain--set-and-refresh-all-font-lock)
 
 (defcustom fountain-align-center
   '(("screenplay" 20)
@@ -517,7 +485,7 @@
 This option does not affect file contents."
   :type '(choice integer
                  (repeat (group (string :tag "Format") integer)))
-  :group 'fountain-align)
+  :set #'fountain--set-and-refresh-all-font-lock)
 
 (defcustom fountain-display-scene-numbers-in-margin
   nil
@@ -527,25 +495,21 @@
 
 This option does affect file contents."
   :type 'boolean
-  :group 'fountain-align
-  :set (lambda (symbol value)
-         (set-default symbol value)
-         (dolist (buffer (buffer-list))
-           (with-current-buffer buffer
-             (when (eq major-mode 'fountain-mode)
-               (font-lock-refresh-defaults))))))
-
-(define-obsolete-variable-alias 'fountain-align-scene-number
-  'fountain-display-scene-numbers-in-margin "2.3.0")
-
-(defun fountain-get-align (element)
-  "Return ELEMENT align integer based on buffer format."
-  (if (integerp element) element
-    (let ((format (or (plist-get (fountain-read-metadata)
-                                 'format)
-                      "screenplay")))
-      (cadr (or (assoc format element)
-                (car element))))))
+  :safe 'booleanp
+  :set #'fountain--set-and-refresh-all-font-lock)
+
+(defun fountain-get-align (option)
+  "Return OPTION align integer based on script format.
+e.g.
+
+    (fountain-get-align fountain-align-character) -> 20"
+  (if (integerp option)
+      option
+    (cadr (or (assoc (or (plist-get (fountain-read-metadata)
+                                    'format)
+                         fountain-script-format)
+                     option)
+              (car option)))))
 
 
 ;;; Autoinsert
@@ -557,7 +521,7 @@
     "title: " (skeleton-read "Title: " (file-name-base (buffer-name))) | -7 "\n"
     "credit: " (skeleton-read "Credit: " "written by") | -9 "\n"
     "author: " (skeleton-read "Author: " user-full-name) | -9 "\n"
-    "format: " (skeleton-read "Script format: " "screenplay") | -9 "\n"
+    "format: " (skeleton-read "Script format: " fountain-script-format) | -9 "\n"
     "source: " (skeleton-read "Source: ") | -9 "\n"
     "date: " (skeleton-read "Date: " (format-time-string fountain-time-format)) | -7 "\n"
     "contact:\n" ("Contact details, %s: " "    " str | -4 "\n") | -9))
@@ -571,42 +535,29 @@
 (defvar fountain-scene-heading-regexp
   nil
   "Regular expression for matching scene headings.
-Set with `fountain-init-scene-heading-regexp'.
-
-    Group 1: match trimmed whitespace
-    Group 2: match leading . (for forced element)
-    Group 3: match scene heading without scene number (export group)
-    Group 4: match space between heading and scene number
-    Group 5: match first # delimiter
-    Group 6: match scene number
-    Group 7: match last # delimiter
+
+    Group 1: match leading . (for forced scene heading)
+    Group 2: match whole scene heading without scene number
+    Group 3: match INT/EXT
+    Group 4: match location
+    Group 5: match time of day
+    Group 6: match space between scene heading and scene number
+    Group 7: match first # delimiter
+    Group 8: match scene number
+    Group 9: match last # delimiter
 
 Requires `fountain-match-scene-heading' for preceding blank line.")
 
-(defvar fountain-scene-number-regexp
-  "\\(?4:[\s\t]+\\)\\(?5:#\\)\\(?6:[a-z0-9\\.-]+\\)\\(?7:#\\)"
-  "Regular expression for matching scene numbers.
-
-    Group 4: match space before scene number
-    Group 5: match first # delimiter
-    Group 6: match scene number
-    Group 7: match last # delimiter")
-
 (defvar fountain-trans-regexp
   nil
   "Regular expression for matching transitions.
 
-    Group 1: match trimmed whitespace
-    Group 2: match forced transition mark
-    Group 3: match transition (export group)
+    Group 1: match forced transition mark
+    Group 2: match transition
 
 Set with `fountain-init-trans-regexp'. Requires
 `fountain-match-trans' for preceding and succeeding blank lines.")
 
-(defconst fountain-blank-regexp
-  "^\s?$"
-  "Regular expression for matching an empty line.")
-
 (defconst fountain-action-regexp
   "^\\(!\\)?\\(.*\\)[\s\t]*$"
   "Regular expression for forced action.
@@ -621,7 +572,7 @@
   "Regular expression for matching comments.")
 
 (defconst fountain-metadata-regexp
-  (concat "^\\(?1:\\(?2:[^:\n]+\\):[\s\t]*\\(?3:.+\\)?\\)"
+  (concat "^\\(?1:\\(?2:[^[{:\n]+\\):[\s\t]*\\(?3:.+\\)?\\)"
           "\\|"
           "^[\s\t]+\\(?1:\\(?3:.+\\)\\)")
   "Regular expression for matching multi-line metadata values.
@@ -631,7 +582,7 @@
   (concat "^[\s\t]*\\(?1:\\(?:"
           "\\(?2:@\\)\\(?3:\\(?4:[^<>\n]+?\\)\\(?:[\s\t]*(.*?)\\)*?\\)"
           "\\|"
-          "\\(?3:\\(?4:[^a-z<>\n]*?[A-Z][^a-z<>\n]*?\\)\\(?:[\s\t]*(.*?)\\)*?\\)"
+          "\\(?3:\\(?4:[^!#a-z<>\n]*?[A-Z][^a-z<>\n]*?\\)\\(?:[\s\t]*(.*?)\\)*?\\)"
           "\\)[\s\t]*\\(?5:\\^\\)?\\)[\s\t]*$")
   "Regular expression for matching character names.
 
@@ -670,13 +621,6 @@
     Group 1: leading ===
     Group 2: forced page number (export group)")
 
-(defconst fountain-end-regexp
-  "^[\s\t]*\\(=\\{3,\\}\\)[\s\t]*\\(end\\)\\>.*$"
-  "Regular expression for matching script end break.
-
-    Group 1: leading ===
-    Group 2: end")
-
 (defconst fountain-note-regexp
   "\\(\\[\\[[\s\t]*\\(\\(?:.\n?\\)*?\\)[\s\t]*]]\\)"
   "Regular expression for matching notes.
@@ -685,12 +629,11 @@
     Group 2: note (export group)")
 
 (defconst fountain-section-heading-regexp
-  "^\\(?1:\\(?2:#\\{1,5\\}\\)[\s\t]*\\(?3:[^#\n].*?\\)\\)[\s\t]*$"
+  "^\\(?1:#\\{1,5\\}\\)[\s\t]*\\(?2:[^#\n].*?\\)[\s\t]*$"
   "Regular expression for matching section headings.
 
-    Group 1: match trimmed whitespace
-    Group 2: match leading #'s
-    Group 3: match heading (export group)")
+    Group 1: match leading #'s
+    Group 2: match heading")
 
 (defconst fountain-synopsis-regexp
   "^\\(\\(=[\s\t]*\\)\\([^=\n].+?\\)\\)[\s\t]*$"
@@ -747,15 +690,11 @@
           "\\(?3:.+\\)")
   "Regular expression for matching lyrics.")
 
-(defconst fountain-template-key-regexp
-  "{{\\([^{}\n]+?\\)}}"
-  "Regular expression key for making template replacements.")
-
 
 ;;; Faces
 
 (defgroup fountain-faces ()
-  "Faces used in `fountain-mode'.
+  "\\<fountain-mode-map>Faces used in `fountain-mode'.
 There are three levels of `font-lock-mode' decoration:
 
     1 (minimum):
@@ -795,124 +734,168 @@
 
 (defface fountain
   '((t nil))
-  "Default base-level face for `fountain-mode' buffers."
-  :group 'fountain-faces)
+  "Default base-level face for `fountain-mode' buffers.")
 
 (defface fountain-action
   '((t nil))
-  "Default face for action."
-  :group 'fountain-faces)
+  "Default face for action.")
 
 (defface fountain-comment
   '((t (:inherit shadow)))
-  "Default face for comments (boneyard)."
-  :group 'fountain-faces)
+  "Default face for comments (boneyard).")
 
 (defface fountain-non-printing
   '((t (:inherit fountain-comment)))
-  "Default face for emphasis delimiters and syntax characters."
-  :group 'fountain-faces)
+  "Default face for emphasis delimiters and syntax characters.")
 
 (defface fountain-metadata-key
   '((t (:inherit font-lock-constant-face)))
-  "Default face for metadata keys."
-  :group 'fountain-faces)
+  "Default face for metadata keys.")
 
 (defface fountain-metadata-value
   '((t (:inherit font-lock-keyword-face)))
-  "Default face for metadata values."
-  :group 'fountain-faces)
+  "Default face for metadata values.")
 
 (defface fountain-page-break
   '((t (:inherit font-lock-constant-face)))
-  "Default face for page breaks."
-  :group 'fountain-faces)
+  "Default face for page breaks.")
 
 (defface fountain-page-number
   '((t (:inherit font-lock-warning-face)))
-  "Default face for page numbers."
-  :group 'fountain-faces)
+  "Default face for page numbers.")
 
 (defface fountain-scene-heading
   '((t (:inherit font-lock-function-name-face)))
-  "Default face for scene headings."
-  :group 'fountain-faces)
+  "Default face for scene headings.")
 
 (defface fountain-paren
   '((t (:inherit font-lock-builtin-face)))
-  "Default face for parentheticals."
-  :group 'fountain-faces)
+  "Default face for parentheticals.")
 
 (defface fountain-center
   '((t nil))
-  "Default face for centered text."
-  :group 'fountain-faces)
+  "Default face for centered text.")
 
 (defface fountain-note
   '((t (:inherit font-lock-comment-face)))
-  "Default face for notes."
-  :group 'fountain-faces)
+  "Default face for notes.")
 
 (defface fountain-section-heading
   '((t (:inherit font-lock-keyword-face)))
-  "Default face for section headings."
-  :group 'fountain-faces)
+  "Default face for section headings.")
 
 (defface fountain-synopsis
   '((t (:inherit font-lock-type-face)))
-  "Default face for synopses."
-  :group 'fountain-faces)
+  "Default face for synopses.")
 
 (defface fountain-character
   '((t (:inherit font-lock-variable-name-face)))
-  "Default face for characters."
-  :group 'fountain-faces)
+  "Default face for characters.")
 
 (defface fountain-dialog
   '((t (:inherit font-lock-string-face)))
-  "Default face for dialog."
-  :group 'fountain-faces)
+  "Default face for dialog.")
 
 (defface fountain-trans
   '((t (:inherit font-lock-builtin-face)))
-  "Default face for transitions."
-  :group 'fountain-faces)
-
-(defface fountain-include
-  '((t (:inherit font-lock-warning-face)))
-  "Default face for file inclusions."
-  :group 'fountain-faces)
+  "Default face for transitions.")
+
+(defface fountain-template
+  '((t (:inherit font-lock-preprocessor-face)))
+  "Default face for template keys.")
 
 (defface fountain-auto-upcase-highlight
   '((t (:inherit hi-yellow)))
-  "Default face for highlighting line for auto-upcasing."
-  :group 'fountain-faces)
+  "Default face for highlighting line for auto-upcasing.")
 
 
 ;;; Initializing
 
+(defcustom fountain-scene-heading-prefix-list
+  '("INT" "EXT" "EST" "INT./EXT." "INT/EXT" "I/E")
+  "List of scene heading prefixes (case insensitive).
+Any scene heading prefix can be followed by a dot and/or a space,
+so the following are equivalent:
+
+    INT HOUSE - DAY
+
+    INT. HOUSE - DAY"
+  :type '(repeat (string :tag "Prefix"))
+  :group 'fountain
+  :set (lambda (symbol value)
+         (set-default symbol value)
+         ;; Don't call fountain-init-*' while in the middle of
+         ;; loading this file!
+         (when (featurep 'fountain-mode)
+           (fountain-init-scene-heading-regexp)
+           (dolist (buffer (buffer-list))
+             (with-current-buffer buffer
+               (when (derived-mode-p 'fountain-mode)
+                 (fountain-init-outline-regexp)
+                 (font-lock-refresh-defaults)))))))
+
+(defcustom fountain-trans-suffix-list
+  '("TO:" "WITH:" "FADE OUT" "TO BLACK")
+  "List of transition suffixes (case insensitive).
+This list is used to match the endings of transitions,
+e.g. `TO:' will match both the following:
+
+    CUT TO:
+
+    DISSOLVE TO:"
+  :type '(repeat (string :tag "Suffix"))
+  :group 'fountain
+  :set (lambda (symbol value)
+         (set-default symbol value)
+         ;; Don't call fountain-*' while in the middle of
+         ;; loading this file!
+         (when (featurep 'fountain-mode)
+           (fountain-init-trans-regexp)
+           (dolist (buffer (buffer-list))
+             (with-current-buffer buffer
+               (when (derived-mode-p 'fountain-mode)
+                 (font-lock-refresh-defaults)))))))
+
 (defun fountain-init-scene-heading-regexp ()
   "Initialize scene heading regular expression.
 Uses `fountain-scene-heading-prefix-list' to create non-forced
 scene heading regular expression."
   (setq fountain-scene-heading-regexp
         (concat
-         ;; First match forced scene heading.
-         "^\\(?1:\\(?2:\\.\\)\\(?3:\\<.*?\\)"
-         "\\(?:" fountain-scene-number-regexp "\\)?"
-         "\\)[\s\t]*$"
-         ;; Or match omitted scene.
-         "\\|"
-         "^\\(?1:\\(?3:OMIT\\(?:TED\\)?\\)"
-         "\\(?:" fountain-scene-number-regexp "\\)?"
-         "\\)[\s\t]*$"
-         ;; Or match regular scene heading.
-         "\\|"
-         "^\\(?1:\\(?3:"
+         "^\\(?:"
+         ;;; Match forced scene heading
+         ;; Group 1: match leading . (for forced scene heading)
+         "\\(?1:\\.\\)"
+         ;; Group 2: match scene heading without scene number
+         "\\(?2:\\<"
+         ;; Group 4: match location
+         "\\(?4:.+?\\)[\s\t]*"
+         ;; Group 5: match time of day
+         "\\(?:--?[\s\t]*\\(?5:.+?\\)\\)?"
+         "\\)\\|"
+         ;;; Match normal scene heading
+         ;; Group 2: match scene heading without scene number
+         "^\\(?2:"
+         ;; Group 3: match INT/EXT
+         "\\(?3:"
          (regexp-opt fountain-scene-heading-prefix-list)
-         "[.\s\t].*?\\)"
-         "\\(?:" fountain-scene-number-regexp "\\)?"
-         "\\)[\s\t]*$")))
+         "\\)[.\s\t][\s\t]*"
+         ;; Group 4: match location
+         "\\(?4:.+?\\)[\s\t]*"
+         ;; Group 5: match time of day
+         "\\(?:--?[\s\t]*\\(?5:.+?\\)\\)?"
+         "\\)\\)"
+         ;;; Match scene number
+         "\\(?:"
+         ;; Group 6: match space between scene heading and scene number
+         "\\(?6:[\s\t]+\\)"
+         ;; Group 7: match first # delimiter
+         "\\(?7:#\\)"
+         ;; Group 8: match scene number
+         "\\(?8:[0-9a-z\\.-]+\\)"
+         ;; Group 9: match last # delimiter
+         "\\(?9:#\\)\\)?"
+         "[\s\t]*$")))
 
 (defun fountain-init-trans-regexp ()
   "Initialize transition regular expression.
@@ -920,30 +903,36 @@
 regular expression."
   (setq fountain-trans-regexp
         (concat
-         ;; First match forced transition.
-         "^[\s\t]*\\(?1:\\(?2:>[\s\t]*\\)\\(?3:[^<>\n]*?\\)\\)[\s\t]*$"
-         ;; Or match regular transition.
+         "^\\(?:[\s\t]*"
+         ;; Match forced transition
+         ;; Group 1: match forced transition mark
+         "\\(?1:>\\)[\s\t]*"
+         ;; Group 2: match transition
+         "\\(?2:[^<>\n]*?\\)"
          "\\|"
-         "^[\s\t]*\\(?1:\\(?3:[[:upper:]\s\t]*"
+         ;; Match normal transition
+         ;; Group 2: match transition
+         "\\(?2:[[:upper:]\s\t]*"
          (upcase (regexp-opt fountain-trans-suffix-list))
-         "\\)\\)[\s\t]*$")))
+         "\\)"
+         "\\)[\s\t]*$")))
 
 (defun fountain-init-outline-regexp ()
   "Initialize `outline-regexp'."
   (setq-local outline-regexp
-              (concat fountain-end-regexp
-                      "\\|"
-                      fountain-section-heading-regexp
+              (concat fountain-section-heading-regexp
                       "\\|"
                       fountain-scene-heading-regexp)))
 
-(defun fountain-init-imenu-generic-expression () ; FIXME: allow user customize
+(defun fountain-init-imenu-generic-expression ()
   "Initialize `imenu-generic-expression'."
+  ;; FIXME: each of these should be a boolean user option to allow the user to
+  ;; choose which appear in the imenu list.
   (setq imenu-generic-expression
         (list
          (list "Notes" fountain-note-regexp 2)
-         (list "Scene Headings" fountain-scene-heading-regexp 3)
-         (list "Sections" fountain-section-heading-regexp 1))))
+         (list "Scene Headings" fountain-scene-heading-regexp 2)
+         (list "Sections" fountain-section-heading-regexp 0))))
 
 (defun fountain-init-vars ()
   "Initialize important variables.
@@ -963,7 +952,10 @@
   (setq-local page-delimiter fountain-page-break-regexp)
   (setq-local outline-level #'fountain-outline-level)
   (setq-local require-final-newline mode-require-final-newline)
-  (setq-local completion-cycle-threshold t) ; FIXME: make user option
+  ;; FIXME: `completion-cycle-threshold' is a user option, so hard-coding it to
+  ;; non-nil is dubious. On the other hand, completion without cycling in
+  ;; screenwriting is weird.
+  (setq-local completion-cycle-threshold t)
   (setq-local completion-at-point-functions
               '(fountain-completion-at-point))
   (setq-local font-lock-extra-managed-props
@@ -972,49 +964,10 @@
   (setq font-lock-defaults
         '(fountain-create-font-lock-keywords nil t))
   (add-to-invisibility-spec (cons 'outline t))
-  (if fountain-hide-emphasis-delim
-      (add-to-invisibility-spec 'fountain-emphasis-delim))
-  (if fountain-hide-syntax-chars
-      (add-to-invisibility-spec 'fountain-syntax-chars)))
-
-(defcustom fountain-scene-heading-prefix-list
-  '("INT" "EXT" "INT/EXT" "I/E")
-  "List of scene heading prefixes (case insensitive).
-Any scene heading prefix can be followed by a dot and/or a space,
-so the following are equivalent:
-
-    INT HOUSE - DAY
-
-    INT. HOUSE - DAY"
-  :type '(repeat (string :tag "Prefix"))
-  :group 'fountain
-  :set (lambda (symbol value)
-         (set-default symbol value)
-         (fountain-init-scene-heading-regexp)
-         (dolist (buffer (buffer-list))
-           (with-current-buffer buffer
-             (when (eq major-mode 'fountain-mode)
-               (fountain-init-outline-regexp)
-               (font-lock-refresh-defaults))))))
-
-(defcustom fountain-trans-suffix-list
-  '("TO:" "WITH:" "FADE OUT" "TO BLACK")
-  "List of transition suffixes (case insensitive).
-This list is used to match the endings of transitions,
-e.g. `TO:' will match both the following:
-
-    CUT TO:
-
-    DISSOLVE TO:"
-  :type '(repeat (string :tag "Suffix"))
-  :group 'fountain
-  :set (lambda (symbol value)
-         (set-default symbol value)
-         (fountain-init-trans-regexp)
-         (dolist (buffer (buffer-list))
-           (with-current-buffer buffer
-             (when (eq major-mode 'fountain-mode)
-               (font-lock-refresh-defaults))))))
+  (when fountain-hide-emphasis-delim
+    (add-to-invisibility-spec 'fountain-emphasis-delim))
+  (when fountain-hide-syntax-chars
+    (add-to-invisibility-spec 'fountain-syntax-chars)))
 
 
 ;;; Emacs Bugs
@@ -1024,6 +977,7 @@
   "If non-nil, attempt to patch known bugs in Emacs.
 See function `fountain-patch-emacs-bugs'."
   :type 'boolean
+  :safe 'booleanp
   :group 'fountain)
 
 (defun fountain-patch-emacs-bugs ()
@@ -1041,7 +995,7 @@
   ;; We want to only return non-nil if property is 'outline
   (unless (or (advice-member-p 'fountain-outline-invisible-p 'outline-invisible-p)
               (<= 26 emacs-major-version))
-    (advice-add 'outline-invisible-p :override 'fountain-outline-invisible-p)
+    (advice-add 'outline-invisible-p :override #'fountain-outline-invisible-p)
     ;; Because `outline-invisible-p' is an inline function, we need to
     ;; reevaluate those functions that called the original bugged version.
     ;; This is impossible for users who have installed Emacs without
@@ -1053,7 +1007,7 @@
         (let ((source (find-function-noselect fun)))
           (with-current-buffer (car source)
             (goto-char (cdr source))
-            (eval (read (current-buffer))))))
+            (eval (read (current-buffer)) lexical-binding))))
       (message "fountain-mode: Function `outline-invisible-p' has been patched"))))
 
 
@@ -1064,11 +1018,11 @@
   (save-excursion
     (save-restriction
       (widen)
-      (forward-line 0)
+      (beginning-of-line)
       (or (bobp)
           (progn (forward-line -1)
                  (or (and (bolp) (eolp))
-                     (progn (end-of-line 1)
+                     (progn (end-of-line)
                             (forward-comment -1))))))))
 
 (defun fountain-blank-after-p ()
@@ -1076,7 +1030,7 @@
   (save-excursion
     (save-restriction
       (widen)
-      (forward-line 1)
+      (forward-line)
       (or (eobp)
           (and (bolp) (eolp))
           (forward-comment 1)))))
@@ -1086,7 +1040,7 @@
   (save-excursion
     (save-restriction
       (widen)
-      (forward-line 0)
+      (beginning-of-line)
       (and (looking-at fountain-metadata-regexp)
            (or (bobp)
                (save-match-data
@@ -1097,7 +1051,7 @@
   (save-excursion
     (save-restriction
       (widen)
-      (forward-line 0)
+      (beginning-of-line)
       (looking-at fountain-page-break-regexp))))
 
 (defun fountain-match-section-heading ()
@@ -1105,7 +1059,7 @@
   (save-excursion
     (save-restriction
       (widen)
-      (forward-line 0)
+      (beginning-of-line)
       (looking-at fountain-section-heading-regexp))))
 
 (defun fountain-match-synopsis ()
@@ -1113,7 +1067,7 @@
   (save-excursion
     (save-restriction
       (widen)
-      (forward-line 0)
+      (beginning-of-line)
       (looking-at fountain-synopsis-regexp))))
 
 (defun fountain-match-note ()
@@ -1121,7 +1075,7 @@
   (save-excursion
     (save-restriction
       (widen)
-      (forward-line 0)
+      (beginning-of-line)
       (or (looking-at fountain-note-regexp)
           (let ((x (point)))
             (and (re-search-backward "\\[\\[" nil t)
@@ -1132,8 +1086,9 @@
   "Match scene heading if point is at a scene heading, nil otherwise."
   (save-excursion
     (save-restriction
+      ;; Widen the restriction to ensure the previous line really is blank.
       (widen)
-      (forward-line 0)
+      (beginning-of-line)
       (and (looking-at fountain-scene-heading-regexp)
            (fountain-blank-before-p)))))
 
@@ -1141,17 +1096,15 @@
   "Match character if point is at character, nil otherwise."
   (unless (fountain-match-scene-heading)
     (save-excursion
-      (forward-line 0)
-      (and (not (and (looking-at fountain-action-regexp)
-                     (match-string 1)))
-           (let ((case-fold-search nil))
+      (beginning-of-line)
+      (and (let ((case-fold-search nil))
              (looking-at fountain-character-regexp))
            (save-match-data
              (save-restriction
                (widen)
                (and (fountain-blank-before-p)
                     (save-excursion
-                      (forward-line 1)
+                      (forward-line)
                       (unless (eobp)
                         (not (and (bolp) (eolp))))))))))))
 
@@ -1164,7 +1117,7 @@
     (save-excursion
       (save-restriction
         (widen)
-        (forward-line 0)
+        (beginning-of-line)
         (and (looking-at fountain-dialog-regexp)
              (save-match-data
                (unless (bobp)
@@ -1178,7 +1131,7 @@
   (save-excursion
     (save-restriction
       (widen)
-      (forward-line 0)
+      (beginning-of-line)
       (and (looking-at fountain-paren-regexp)
            (save-match-data
              (unless (bobp)
@@ -1191,7 +1144,7 @@
   (save-excursion
     (save-restriction
       (widen)
-      (forward-line 0)
+      (beginning-of-line)
       (and (let (case-fold-search)
              (looking-at fountain-trans-regexp))
            (fountain-blank-before-p)
@@ -1203,7 +1156,7 @@
   (save-excursion
     (save-restriction
       (widen)
-      (forward-line 0)
+      (beginning-of-line)
       (looking-at fountain-center-regexp))))
 
 (defun fountain-match-action ()
@@ -1212,13 +1165,13 @@
   (save-excursion
     (save-restriction
       (widen)
-      (forward-line 0)
+      (beginning-of-line)
       (or (and (looking-at fountain-action-regexp)
                (match-string 1))
           (and (not (or (and (bolp) (eolp))
                         (fountain-match-section-heading)
                         (fountain-match-scene-heading)
-                        (fountain-match-include)
+                        (fountain-match-template)
                         (fountain-match-page-break)
                         (fountain-match-character)
                         (fountain-match-dialog)
@@ -1257,55 +1210,62 @@
 (defvar-local fountain-completion-characters
   nil
   "List of characters in the current buffer.
-Each element is a cons of the character name, a string, and the
-character's priority, an integer.
+Each element is a cons (NAME . PRIORITY)  where NAME is a string, and
+PRIORITY is an integer.
 
 n.b. The priority value does not equate to the number of lines
 the character has.")
 
+(defvar-local fountain--edit-line
+  nil
+  "Line number currently being edited.
+Prevents incomplete strings added to candidates.")
+
 (defun fountain-completion-update-scene-headings (start end)
   "Update `fountain-completion-scene-headings' between START and END.
 
 Added to `jit-lock-functions'."
+  ;; FIXME: Doing this within jit-lock is unreliable.  It should be done within
+  ;; *-change-functions instead!
   (goto-char end)
   (if (fountain-match-scene-heading)
-      (forward-line 1)
+      (forward-line)
     (fountain-forward-scene 1))
   (setq end (point))
   (goto-char start)
   (fountain-forward-scene 0)
   (while (< (point) end)
-    (if (and (not (and (integerp fountain--edit-line)
-                       (= fountain--edit-line (line-number-at-pos))))
-             (fountain-match-scene-heading))
-        (let ((scene-heading (match-string-no-properties 3)))
-          (unless (member scene-heading fountain-completion-scene-headings)
-            (push scene-heading fountain-completion-scene-headings))))
+    (when (and (not (and (integerp fountain--edit-line)
+                         (= fountain--edit-line (line-number-at-pos))))
+               (fountain-match-scene-heading))
+      (let ((scene-heading (match-string-no-properties 2)))
+        (unless (member scene-heading fountain-completion-scene-headings)
+          (push scene-heading fountain-completion-scene-headings))))
     (fountain-forward-scene 1)))
 
 (defun fountain-completion-update-characters (start end)
   "Update `fountain-completion-characters' between START and END.
 
 Added to `jit-lock-functions'."
+  ;; FIXME: Doing this within jit-lock is unreliable.  It should be done within
+  ;; *-change-functions instead!
   (goto-char end)
   (if (fountain-match-scene-heading)
-      (forward-line 1)
+      (forward-line)
     (fountain-forward-scene 1))
   (setq end (point))
   (goto-char start)
   (fountain-forward-scene 0)
   (while (< (point) end)
-    (if (and (not (and (integerp fountain--edit-line)
-                       (= fountain--edit-line (line-number-at-pos))))
-             (fountain-match-character))
-        (let* ((character (match-string-no-properties 4))
-               (candidate (assoc-string character fountain-completion-characters))
-               (n (cdr candidate)))
-          (if (not n)
-              (push (cons character 1) fountain-completion-characters)
-            (setq fountain-completion-characters
-                  (delete candidate fountain-completion-characters))
-            (push (cons character (1+ n)) fountain-completion-characters))))
+    (when (and (not (and (integerp fountain--edit-line)
+                         (= fountain--edit-line (line-number-at-pos))))
+               (fountain-match-character))
+      (let* ((character (match-string-no-properties 4))
+             (candidate (assoc-string character fountain-completion-characters))
+             (n (cdr candidate)))
+        (if (not n)
+            (push (cons character 1) fountain-completion-characters)
+          (cl-incf (cdr candidate)))))
     (fountain-forward-character 1))
   (setq fountain-completion-characters
         (sort fountain-completion-characters #'(lambda (a b)
@@ -1318,34 +1278,42 @@
 previously speaking character within scene. After that, return
 characters from `fountain-completion-characters'."
   (lambda (string pred action)
-    (let (candidates)
+    (let (scene-characters alt-character contd-character rest-characters)
       (save-excursion
         (save-restriction
           (widen)
-          (fountain-forward-character 0)
+          (fountain-forward-character 0 'scene)
           (while (not (or (fountain-match-scene-heading)
                           (bobp)))
-            (if (fountain-match-character)
-                (let ((character (match-string-no-properties 4)))
-                  (unless (member character candidates)
-                    (push (list character) candidates))))
+            (when (fountain-match-character)
+              (let ((character (match-string-no-properties 4)))
+                (unless (member character scene-characters)
+                  (push (list character) scene-characters))))
             (fountain-forward-character -1 'scene))))
-      (setq candidates (reverse candidates))
-      (let ((contd-character (list (car candidates)))
-            (alt-character (list (car (cdr candidates))))
-            (rest-characters (cdr (cdr candidates))))
-        (setq candidates (append alt-character contd-character rest-characters)))
-      (setq candidates (append candidates
-                               fountain-completion-characters))
+      (setq scene-characters (reverse scene-characters)
+            alt-character (cadr scene-characters)
+            contd-character (car scene-characters)
+            rest-characters (cddr scene-characters)
+            scene-characters nil)
+      (when rest-characters
+        (setq scene-characters rest-characters))
+      (when contd-character
+        (setq scene-characters
+              (cons contd-character scene-characters)))
+      (when alt-character
+        (setq scene-characters
+              (cons alt-character scene-characters)))
       (if (eq action 'metadata)
           (list 'metadata
                 (cons 'display-sort-function 'identity)
                 (cons 'cycle-sort-function 'identity))
-        (complete-with-action action candidates string pred)))))
+        (complete-with-action action
+                              (append scene-characters fountain-completion-characters)
+                              string pred)))))
 
 (defun fountain-completion-at-point ()
-  "Return completion table for entity at point.
-Trigger completion with `completion-at-point' (\\[completion-at-point]).
+  "\\<fountain-mode-map>Return completion table for entity at point.
+Trigger completion with \\[completion-at-point].
 
 Always delimits entity from beginning of line to point. If at a
 scene heading, return `fountain-scene-heading-candidates'. If
@@ -1400,11 +1368,14 @@
 script, you may get incorrect output."
   :type '(choice integer
                  (list (cons (const :tag "US Letter" letter) integer)
-                       (cons (const :tag "A4" a4) integer)))
-  :group 'fountain-pages)
-
-;; FIXME: timer can be used for things other than page count, e.g. automatically
-;; adding continued dialogue string.
+                       (cons (const :tag "A4" a4) integer))))
+
+(defcustom fountain-pages-ignore-narrowing
+  nil
+  "Non-nil if counting pages should ignore buffer narrowing."
+  :type 'boolean
+  :safe 'booleanp)
+
 (defvar fountain-page-count-timer
   nil)
 
@@ -1414,267 +1385,8 @@
 (defcustom fountain-pages-count-delay
   2.0
   "Idle time in seconds before calculating page count."
-  :type 'float
-  :group 'fountain-pages)
-
-(defun fountain-goto-page-break-point ()
-  "Move point to appropriate place to break a page.
-This is usually before point, but may be after if only skipping
-over whitespace."
-  ;; FIXME: rewrite to account for non-exported elements
-  (if (looking-at "\n[\n\s\t]*\n")
-      (goto-char (match-end 0)))
-  (let ((element (fountain-get-element)))
-    (cond
-     ;; If we're are a section heading, scene heading or character, we can
-     ;; safely break before.
-     ((memq element '(section-heading scene-heading character))
-      (forward-line 0))
-     ;; If we're at a parenthetical, check if the previous line is a character.
-     ;; and if so call recursively on that element.
-     ((eq element 'paren)
-      (forward-line 0)
-      (let ((x (point)))
-        (forward-char -1)
-        (if (fountain-match-character)
-            (progn
-              (forward-line 0)
-              (fountain-goto-page-break-point))
-          ;; Otherwise parenthetical is mid-dialogue, so get character name
-          ;; and break at this element.
-          (goto-char x))))
-     ;; If we're at dialogue, skip over spaces then go to the beginning of the
-     ;; current sentence.
-     ((eq element 'lines)
-      (skip-chars-forward "\s\t")
-      (if (not (looking-back (sentence-end)
-                             (save-excursion
-                               (fountain-forward-character 0)
-                               (point))))
-          (forward-sentence -1)
-        ;; This may move to character element, or back within dialogue. If
-        ;; previous line is a character or parenthetical, call recursively on
-        ;; that element. Otherwise, get character name and break page here.
-        (let ((x (point)))
-          (forward-char -1)
-          (if (or (fountain-match-character)
-                  (fountain-match-paren))
-              (fountain-goto-page-break-point)
-            (goto-char x)))))
-     ;; If we're at a transition or center text, skip backwards to previous
-     ;; element and call recursively on that element.
-     ((memq element '(trans center))
-      (skip-chars-backward "\n\r\s\t")
-      (forward-line 0)
-      (fountain-goto-page-break-point))
-     ;; If we're at action, skip over spaces then go to the beginning of the
-     ;; current sentence.
-     ((eq element 'action)
-      (skip-chars-forward "\s\t")
-      (unless (or (bolp)
-                  (looking-back (sentence-end) nil))
-        (forward-sentence -1))
-      ;; Then, try to skip back to the previous element. If it is a scene
-      ;; heading, call recursively on that element. Otherwise, break page here.
-      (let ((x (point)))
-        (skip-chars-backward "\n\r\s\t")
-        (forward-line 0)
-        (if (fountain-match-scene-heading)
-            (fountain-goto-page-break-point)
-          (goto-char x)))))))
-
-(defun fountain-forward-page (&optional n export-elements)
-  "Move point forward by an approximate page.
-
-Moves forward from point, which is unlikely to correspond to
-final exported pages and so probably should not be used
-interactively.
-
-To considerably speed up this function, supply EXPORT-ELEMENTS
-with `fountain-get-export-elements'."
-  (let ((skip-whitespace-fun
-         (lambda ()
-           (if (looking-at "[\n\s\t]*\n")
-               (goto-char (match-end 0))))))
-    (unless n (setq n 1))
-    (while (< 0 n)
-      ;; Pages don't begin with blank space, so skip over any at point.
-      (funcall skip-whitespace-fun)
-      ;; If we're at a page break, move to its end and skip over whitespace.
-      (when (fountain-match-page-break)
-        (goto-char (match-end 0))
-        (funcall skip-whitespace-fun))
-      ;; Start counting lines.
-      (let ((line-count 0))
-        ;; Begin the main loop, which only halts if we reach the end of buffer,
-        ;; a forced page break, or after the maximum lines in a page.
-        (while (and (< line-count (cdr (assq fountain-export-page-size
-                                             fountain-pages-max-lines)))
-                    (not (eobp))
-                    (not (fountain-match-page-break)))
-          (cond
-           ;; If we're at the end of a line (but not also the beginning, i.e.
-           ;; not a blank line) then move forward a line and increment
-           ;; line-count.
-           ((and (eolp) (not (bolp)))
-            (forward-line 1)
-            (setq line-count (1+ line-count)))
-           ;; If we're looking at newline, skip over it and any whitespace and
-           ;; increment line-count.
-           ((looking-at "[\n\s\t]*\n")
-            (goto-char (match-end 0))
-            (setq line-count (1+ line-count)))
-           ;; We are at an element. Find what kind of element. If it is not
-           ;; included in export, skip over without incrementing line-count
-           ;; (implement with block bounds). Get the line width.
-           (t
-            (let ((element (fountain-get-element)))
-              (if (memq element (or export-elements
-                                    (fountain-get-export-elements)))
-                  (progn
-                    (fountain-move-to-fill-width element)
-                    (setq line-count (1+ line-count)))
-                ;; Element is not exported, so skip it without incrementing
-                ;; line-count.
-                (end-of-line)
-                (funcall skip-whitespace-fun)))))))
-      ;; We are not at the furthest point in a page. Skip over any remaining
-      ;; whitespace, then go back to page-break point.
-      (skip-chars-forward "\n\s\t")
-      (fountain-goto-page-break-point)
-      (setq n (1- n)))))
-
-(defun fountain-move-to-fill-width (element)
-  "Move point to column of ELEMENT fill limit suitable for breaking line.
-Skip over comments."
-  (let ((fill-width
-         (cdr (symbol-value
-               (plist-get (cdr (assq element fountain-elements))
-                          :fill)))))
-    (let ((i 0))
-      (while (and (< i fill-width) (not (eolp)))
-        (cond ((= (syntax-class (syntax-after (point))) 0)
-               (forward-char 1)
-               (setq i (1+ i)))
-              ((forward-comment 1))
-              (t
-               (forward-char 1)
-               (setq i (1+ i))))))
-    (skip-chars-forward "\s\t")
-    (if (eolp) (forward-line 1))
-    (unless (bolp) (fill-move-to-break-point (line-beginning-position)))))
-
-(defun fountain-insert-page-break (&optional string)
-  "Insert a page break at appropriate place preceding point.
-STRING is an optional page number string to force the page
-number."
-  (interactive "sPage number (RET for none): ")
-  ;; Save a marker where we are.
-  (let ((x (point-marker))
-        (page-break
-         (concat "===" (if (< 0 (string-width string))
-                           (concat "\s" string "\s==="))))
-        element)
-    ;; Move point to appropriate place to break page.
-    (fountain-goto-page-break-point)
-    (setq element (fountain-get-element))
-    ;; At this point, element can only be: section-heading, scene-heading,
-    ;; character, action, paren or lines. Only paren and lines require special
-    ;; treatment.
-    (if (memq element '(lines paren))
-        (let ((name (fountain-get-character -1)))
-          (insert (concat
-                   fountain-export-more-dialog-string "\n\n"
-                   page-break "\n\n"
-                   name "\s" fountain-continued-dialog-string "\n")))
-      ;; Otherwise, insert the page break where we are. If the preceding element
-      ;; is a page break, only replace the page number, otherwise, insert the
-      ;; page break.
-      (if (save-excursion
-            (save-restriction
-              (widen)
-              (skip-chars-backward "\n\r\s\t")
-              (fountain-match-page-break)))
-          (replace-match page-break t t)
-        (delete-horizontal-space)
-        (unless (bolp) (insert "\n\n"))
-        (insert-before-markers page-break "\n\n")))
-    ;; Return to where we were.
-    (goto-char x)))
-
-(defun fountain-get-page-count ()
-  (let ((x (point))
-        (total 0)
-        (current 0)
-        (end (point-max))
-        (export-elements (fountain-get-export-elements))
-        found)
-    (save-excursion
-      (save-restriction
-        (widen)
-        (goto-char (point-min))
-        (if (re-search-forward fountain-end-regexp nil t)
-            (setq end (match-beginning 0)))
-        (goto-char (point-min))
-        (while (< (point) end)
-          (fountain-forward-page 1 export-elements)
-          (setq total (1+ total))
-          (if (and (not found) (<= x (point))) (setq current total found t)))
-        (cons current total)))))
-
-(defun fountain-count-pages ()
-  "Return the approximate current page of total pages in current buffer.
-
-If called interactively, print message in echo area.
-
-If point is beyond script end break, current page number is
-returned as 0."
-  (interactive)
-  (fountain-pages-update-mode-line)
-  (redisplay)
-  (let ((pages (fountain-get-page-count)))
-    (fountain-pages-update-mode-line (car pages) (cdr pages))
-    (if (called-interactively-p)
-        (message "Page %d of %d" (car pages) (cdr pages)))))
-
-(defun fountain-pages-update-mode-line (&optional current total)
-  (setq fountain-page-count-string
-        (if fountain-pages-show-in-mode-line
-            (if (and current total)
-                (format "[%d/%d] " current total)
-              "[-/-] ")
-          nil))
-  (force-mode-line-update))
-
-(defun fountain-count-pages-maybe (&optional force)
-  (while-no-input
-    (redisplay)
-    (if (eq major-mode 'fountain-mode)
-        (cond (force
-               (fountain-count-pages))
-              ((eq fountain-pages-show-in-mode-line 'timer)
-               (fountain-count-pages))
-              ((and fountain-page-count-string
-                    (not fountain-pages-show-in-mode-line))
-               (fountain-pages-update-mode-line))))))
-
-(defun fountain-init-mode-line ()
-  (let ((tail (cdr (memq 'mode-line-modes mode-line-format))))
-    (setq mode-line-format
-          (append
-           (butlast mode-line-format (length tail))
-           (cons 'fountain-page-count-string tail)))))
-
-(defun fountain-cancel-page-count-timer ()
-  (if (timerp fountain-page-count-timer)
-      (cancel-timer fountain-page-count-timer))
-  (setq fountain-page-count-timer nil))
-
-(defun fountain-restart-page-count-timer ()
-  (fountain-cancel-page-count-timer)
-  (setq fountain-page-count-timer
-        (run-with-idle-timer fountain-pages-count-delay t
-                             #'fountain-count-pages-maybe)))
+  :type 'number
+  :safe 'numberp)
 
 (defcustom fountain-pages-show-in-mode-line
   nil
@@ -1682,759 +1394,15 @@
   :type '(choice (const :tag "No page count" nil)
                  (const :tag "Show with manual update" force)
                  (const :tag "Show with automatic update" timer))
-  :group 'fountain-pages
+  :safe (lambda (value) (memq value '(nil force timer)))
   :set (lambda (symbol value)
          (set-default symbol value)
-         (dolist (buffer (buffer-list))
-           (with-current-buffer buffer
-             (fountain-count-pages-maybe value)))))
-
-
-;;; Inclusions
-
-(defvar fountain-include-regexp
-  "^[\s\t]*{{[\s\t]*\\(?1:[^:\n]+:\\)?[\s\t]*\\(?2:.+?\\)[\s\t]*}}[\s\t]*")
-
-(defun fountain-match-include ()
-  "Match inclusion if point is at inclusion, nil otherwise."
-  (save-excursion
-    (save-restriction
-      (widen)
-      (forward-line 0)
-      (looking-at fountain-include-regexp))))
-
-(defun fountain-include-find-file (&optional no-select)
-  "Find included file at point.
-
-Optional argument NO-SELECT will find file without selecting
-window."
-  (interactive)
-  (if (and (fountain-match-include)
-           (save-match-data
-             (string-match "include:" (match-string 1))))
-      (let ((filename (expand-file-name (match-string 2))))
-        (if no-select
-            (find-file-noselect filename)
-          (find-file filename)))))
-
-(defun fountain-include-in-region (start end &optional delete)
-  "Replace inclusions between START and END with their file contents.
-
-If optional argument DELETE is non-nil (if prefix with \\[universal-argument]
-when called interactively), delete instead."
-  (interactive "*r\nP")
-  (save-excursion
-    (save-restriction
-      (widen)
-      (goto-char end)
-      (setq end (point-marker))
-      (goto-char start)
-      (while (< (point) (min end (point-max)))
-        (when (fountain-match-include)
-          (if delete
-              (delete-region (match-beginning 0) (match-end 0))
-            (replace-match
-             (save-match-data
-               (with-current-buffer (fountain-include-find-file)
-                 (save-restriction
-                   (widen)
-                   (buffer-substring-no-properties (point-min) (point-max)))))
-             t t)))
-        (forward-line 1)))))
-
-
-;;; Parsing
-
-(require 'subr-x)
-
-(defun fountain-get-character (&optional n limit)
-  "Return Nth next character (or Nth previous if N is negative).
-
-If N is non-nil, return Nth next character or Nth previous
-character if N is negative, otherwise return nil. If N is nil or
-0, return character at point, otherwise return nil.
-
-If LIMIT is 'scene, halt at next scene heading. If LIMIT is
-'dialog, halt at next non-dialog element."
-  (let ((n (or n 0)))
-    (save-excursion
-      (save-restriction
-        (widen)
-        (fountain-forward-character n limit)
-        (if (fountain-match-character)
-            (match-string-no-properties 4))))))
-
-(defun fountain-read-metadata ()
-  "Read metadata of current buffer and return as a property list.
-
-Key string is slugified using `fountain-slugify', and interned.
-Value string remains a string. e.g.
-
-    Draft date: 2015-12-25 -> (draft-date \"2015-12-25\")"
-  (save-excursion
-    (save-restriction
-      (widen)
-      (goto-char (point-min))
-      (let (list)
-        (while (fountain-match-metadata)
-          (let ((key (match-string 2))
-                (value (match-string-no-properties 3)))
-            (forward-line 1)
-            (while (and (fountain-match-metadata)
-                        (null (match-string 2)))
-              (setq value
-                    (concat value (if value "\n")
-                            (match-string-no-properties 3)))
-              (forward-line 1))
-            (setq list
-                  (append list (list (intern (fountain-slugify key))
-                                     value)))))
-        list))))
-
-(defun fountain-dual-dialog (&optional pos)
-  "Non-nil if point or POS is within dual dialogue.
-Returns \"right\" if within right-side dual dialogue, \"left\" if
-within left-side dual dialogue, and nil otherwise."
-  (save-excursion
-    (save-match-data
-      (save-restriction
-        (widen)
-        (if pos (goto-char pos))
-        (cond ((progn (fountain-forward-character 0 'dialog)
-                      (and (fountain-match-character)
-                           (stringp (match-string 5))))
-               'right)
-              ((progn (fountain-forward-character 1 'dialog)
-                      (and (fountain-match-character)
-                           (stringp (match-string 5))))
-               'left))))))
-
-(defun fountain-starts-new-page (&optional limit) ; FIXME: implement LIMIT
-  (save-excursion
-    (save-match-data
-      (save-restriction
-        (widen)
-        (forward-line 0)
-        (skip-chars-backward "\n\r\s\t")
-        (fountain-match-page-break)))))
-
-(defun fountain-parse-section (match-data &optional export-elements job)
-  "Return an element list for matched section heading."
-  (set-match-data match-data)
-  (let* ((beg (match-beginning 0))
-         (starts-new-page (fountain-starts-new-page))
-         (section-heading
-          (list 'section-heading
-                (list 'begin (match-beginning 0)
-                      'end (match-end 0)
-                      'level (save-excursion
-                               (goto-char (match-beginning 0))
-                               (funcall outline-level))
-                      'export (if (memq 'section-heading export-elements) t))
-                (match-string-no-properties 3)))
-         (end (save-excursion (outline-end-of-subtree) (point)))
-         content)
-    (goto-char (plist-get (nth 1 section-heading) 'end))
-    (setq content
-          (fountain-parse-region (point) end export-elements job))
-    (list 'section
-          (list 'begin beg
-                'end end
-                'starts-new-page starts-new-page
-                'export t)
-          (cons section-heading content))))
-
-(defun fountain-parse-scene (match-data &optional export-elements job)
-  "Return an element list for matched scene heading at point.
-Includes child elements."
-  (set-match-data match-data)
-  (let* ((beg (match-beginning 0))
-         (starts-new-page (fountain-starts-new-page))
-         (scene-number
-          (save-excursion
-            (save-match-data
-              (goto-char (match-beginning 0))
-              (fountain-scene-number-to-string
-               (fountain-get-scene-number 0)))))
-         (scene-heading
-          (list 'scene-heading
-                (list 'begin (match-beginning 0)
-                      'end (match-end 0)
-                      'scene-number scene-number
-                      'forced (stringp (match-string 2))
-                      'export (if (memq 'scene-heading export-elements) t)
-                      'starts-new-page starts-new-page)
-                (match-string-no-properties 3)))
-         (end (save-excursion (outline-end-of-subtree) (point)))
-         content)
-    (goto-char (plist-get (nth 1 scene-heading) 'end))
-    (setq content
-          (fountain-parse-region (point) end export-elements job))
-    (list 'scene
-          (list 'begin beg
-                'end end
-                'scene-number scene-number
-                'starts-new-page starts-new-page
-                'export t)
-          (cons scene-heading content))))
-
-(defun fountain-parse-dialog (match-data &optional export-elements job)
-  (set-match-data match-data)
-  (let* ((beg (match-beginning 0))
-         (starts-new-page (fountain-starts-new-page))
-         (dual (fountain-dual-dialog))
-         (character
-          (list 'character
-                (list 'begin (match-beginning 0)
-                      'end (match-end 0)
-                      'forced (stringp (match-string 2))
-                      'export (if (memq 'character export-elements) t)
-                      'starts-new-page (unless (eq dual 'left) starts-new-page))
-                (match-string-no-properties 3)))
-         (end
-          (save-excursion
-            (fountain-forward-character 1 'dialog)
-            (skip-chars-backward "\n\r\s\t")
-            (point)))
-         first-dialog)
-    (goto-char (plist-get (nth 1 character) 'end))
-    ;; Parse the first dialogue tree, which may be the only dialogue tree.
-    (setq first-dialog
-          (list 'dialog
-                (list 'begin beg
-                      'end end
-                      'dual dual
-                      'export (if (or (memq 'character export-elements)
-                                      (memq 'lines export-elements)
-                                      (memq 'paren export-elements))
-                                  t))
-                (cons character
-                      (fountain-parse-region (point) end export-elements job))))
-    ;; If at the first (left) character of dual dialogue, parse a dual-dialogue
-    ;; tree, containing dialogue trees.
-    (if (eq dual 'left)
-        ;; Find the end of the dual-dialogue.
-        (let ((end
-               (save-excursion
-                 (while (fountain-dual-dialog)
-                   (fountain-forward-character 1 'dialog))
-                 (skip-chars-backward "\n\r\s\t")
-                 (point))))
-          ;; Return the dual-dialogue tree.
-          (list 'dual-dialog
-                (list 'begin beg
-                      'end end
-                      'starts-new-page starts-new-page
-                      'export (if (or (memq 'character export-elements)
-                                      (memq 'lines export-elements)
-                                      (memq 'paren export-elements))
-                                  t))
-                ;; Add the first dialogue block to the head of the dual-dialogue
-                ;; tree.
-                (cons first-dialog
-                      ;; Parse the containing region.
-                      (fountain-parse-region
-                       (plist-get (nth 1 first-dialog) 'end)
-                       end export-elements job))))
-      ;; Otherwise, return the first dialogue tree.
-      first-dialog)))
-
-(defun fountain-parse-lines (match-data &optional export-elements job)
-  "Return an element list for matched dialogue."
-  (set-match-data match-data)
-  (let ((beg (match-beginning 0))
-        (end (match-end 0)))
-    (list 'lines
-          (list 'begin beg
-                'end end
-                'export (if (memq 'lines export-elements) t))
-          (match-string-no-properties 1))))
-
-(defun fountain-parse-paren (match-data &optional export-elements job)
-  "Return an element list for matched parenthetical."
-  (set-match-data match-data)
-  (list 'paren
-        (list 'begin (match-beginning 0)
-              'end (match-end 0)
-              'export (if (memq 'paren export-elements) t))
-        (match-string-no-properties 1)))
-
-(defun fountain-parse-trans (match-data &optional export-elements job)
-  "Return an element list for matched transition."
-  (set-match-data match-data)
-  (list 'trans
-        (list 'begin (match-beginning 0)
-              'end (match-end 0)
-              'forced (stringp (match-string 2))
-              'export (if (memq 'trans export-elements) t)
-              'starts-new-page (fountain-starts-new-page))
-        (match-string-no-properties 3)))
-
-(defun fountain-parse-center (match-data &optional export-elements job)
-  "Return an element list for matched center text."
-  (list 'center
-        (list 'begin (match-beginning 0)
-              'end (match-end 0)
-              'export (if (memq 'center export-elements) t)
-              'starts-new-page (fountain-starts-new-page))
-        (match-string-no-properties 3)))
-
-(defun fountain-parse-page-break (match-data &optional export-elements job)
-  "Return an element list for matched page break."
-  (set-match-data match-data)
-  (list 'page-break
-        (list 'begin (match-beginning 0)
-              'end (match-end 0)
-              'export (if (memq 'page-break export-elements) t))
-        (match-string-no-properties 2)))
-
-(defun fountain-parse-synopsis (match-data &optional export-elements job)
-  "Return an element list for matched synopsis."
-  (set-match-data match-data)
-  (list 'synopsis
-        (list 'begin (match-beginning 0)
-              'end (match-end 0)
-              'export (if (memq 'synopsis export-elements) t)
-              'starts-new-page (fountain-starts-new-page))
-        (match-string-no-properties 3)))
-
-(defun fountain-parse-note (match-data &optional export-elements job)
-  "Return an element list for matched note."
-  (set-match-data match-data)
-  (list 'note
-        (list 'begin (match-beginning 0)
-              'end (match-end 0)
-              'export (if (memq 'note export-elements) t)
-              'starts-new-page (fountain-starts-new-page))
-        (match-string-no-properties 2)))
-
-(defun fountain-parse-action (match-data &optional export-elements job)
-  "Return an element list for matched action."
-  (set-match-data match-data)
-  (let ((beg (match-beginning 0))
-        (end
-         (save-excursion
-           (save-match-data
-             (goto-char (match-beginning 0))
-             (re-search-forward fountain-blank-regexp nil 'move)
-             (skip-chars-backward "\n\r\s\t")
-             (point))))
-        string)
-    (setq string (buffer-substring-no-properties (match-beginning 2) end)
-          string (replace-regexp-in-string "^!" "" string))
-    (list 'action
-          (list 'begin beg
-                'end end
-                'forced (stringp (match-string 1))
-                'export (if (memq 'action export-elements) t)
-                'starts-new-page (fountain-starts-new-page))
-          string)))
-
-(defun fountain-parse-element (&optional export-elements job)
-  "Call appropropriate element parsing function for matched element at point."
-  (let ((parser (plist-get (cdr (assq (fountain-get-element)
-                                      fountain-elements))
-                           :parser)))
-    (if parser (funcall parser (match-data) export-elements job))))
-
-(defun fountain-parse-region (start end export-elements job)
-  "Return a list of parsed element lists in region between START and END."
-  (goto-char start)
-  (setq end (min end (point-max)))
-  (let (list)
-    (while (< (point) end)
-      (skip-chars-forward "\n\r\s\t")
-      (forward-line 0)
-      (if (< (point) end)
-          (let ((element (fountain-parse-element export-elements job)))
-            (push element list)
-            ;; FIXME: better to use a forward-block function
-            (goto-char (plist-get (nth 1 element) 'end))))
-      (if job (progress-reporter-update job)))
-    (reverse list)))
-
-(defun fountain-prep-and-parse-region (start end)
-  "Prepare and parse region between START and END."
-  (let ((buffer (current-buffer))
-        (export-elements (fountain-get-export-elements))
-        (job (make-progress-reporter "Parsing...")))
-    (prog1
-        (with-temp-buffer
-          (fountain-init-vars)
-          (insert-buffer-substring buffer start end)
-          (fountain-include-in-region (point-min) (point-max)
-                                      (not (memq 'include export-elements)))
-          (fountain-delete-comments-in-region (point-min) (point-max))
-          ;; Delete metadata.
-          (goto-char (point-min))
-          (while (fountain-match-metadata)
-            (forward-line 1))
-          (delete-region (point-min) (point))
-          ;; Search for script end point and delete beyond.
-          (if (re-search-forward fountain-end-regexp nil t)
-              (delete-region (match-beginning 0) (point-max)))
-          (fountain-parse-region (point-min) (point-max) export-elements job))
-      (progress-reporter-done job))))
-
-
-;;; Filling
-
-(defgroup fountain-fill ()
-  "Options for filling elements.
-
-Filling elements is used in exporting to plaintext and
-PostScript, and in calculating page length for page locking."
-  :prefix "fountain-fill-"
-  :group 'fountain-export)
-
-(defcustom fountain-fill-section-heading
-  (cons 0 61)
-  "Cons cell of integers for indenting and filling section headings.
-The car sets `left-margin' and cdr `fill-column'."
-  :type '(cons (integer :tag "Indent")
-               (integer :tag "Width"))
-  :group 'fountain-fill)
-
-(defcustom fountain-fill-scene-heading
-  (cons 0 61)
-  "Cons cell of integers for indenting and filling scene headings.
-The car sets `left-margin' and cdr `fill-column'."
-  :type '(cons (integer :tag "Indent")
-               (integer :tag "Width"))
-  :group 'fountain-fill)
-
-(defcustom fountain-fill-action
-  (cons 0 61)
-  "Cons cell of integers for indenting and filling action.
-The car sets `left-margin' and cdr `fill-column'."
-  :type '(cons (integer :tag "Indent")
-               (integer :tag "Width"))
-  :group 'fountain-fill)
-
-(defcustom fountain-fill-character
-  (cons 20 38)
-  "Cons cell of integers for indenting and filling character.
-The car sets `left-margin' and cdr `fill-column'."
-  :type '(cons (integer :tag "Indent")
-               (integer :tag "Width"))
-  :group 'fountain-fill)
-
-(defcustom fountain-fill-paren
-  (cons 15 26)
-  "Cons cell of integers for indenting and filling parenthetical.
-The car sets `left-margin' and cdr `fill-column'."
-  :type '(cons (integer :tag "Indent")
-               (integer :tag "Width"))
-  :group 'fountain-fill)
-
-(defcustom fountain-fill-dialog
-  (cons 10 35)
-  "Cons cell of integers for indenting and filling dialogue.
-The car sets `left-margin' and cdr `fill-column'."
-  :type '(cons (integer :tag "Indent")
-               (integer :tag "Width"))
-  :group 'fountain-fill)
-
-(defcustom fountain-fill-trans
-  (cons 42 16)
-  "Cons cell of integers for indenting and filling transition.
-The car sets `left-margin' and cdr `fill-column'."
-  :type '(cons (integer :tag "Indent")
-               (integer :tag "Width"))
-  :group 'fountain-fill)
-
-(defcustom fountain-fill-synopsis
-  (cons 0 61)
-  "Cons cell of integers for indenting and filling synopses.
-The car sets `left-margin' and cdr `fill-column'."
-  :type '(cons (integer :tag "Indent")
-               (integer :tag "Width"))
-  :group 'fountain-fill)
-
-(defcustom fountain-fill-note
-  (cons 0 61)
-  "Cons cell of integers for indenting and filling notes.
-The car sets `left-margin' and cdr `fill-column'."
-  :type '(cons (integer :tag "Indent")
-               (integer :tag "Width"))
-  :group 'fountain-fill)
-
-
-;;; Exporting
-
-(defgroup fountain-export ()
-  "Options for exporting Fountain files."
-  :prefix "fountain-export-"
-  :group 'fountain)
-
-(defcustom fountain-export-include-elements
-  '(("screenplay" scene-heading action character lines paren trans center page-break include)
-    ("teleplay" section-heading scene-heading action character lines paren trans center page-break include)
-    ("stageplay" section-heading scene-heading action character lines paren trans center page-break include))
-  "Association list of elements to include when exporting.
-Note that comments (boneyard) are never included."
-  :type '(alist :key-type (string :tag "Format")
-                :value-type (set :tag "Elements"
-                                 (const :tag "Section Headings" section-heading)
-                                 (const :tag "Scene Headings" scene-heading)
-                                 (const :tag "Action" action)
-                                 (const :tag "Character Names" character)
-                                 (const :tag "Dialogue" lines)
-                                 (const :tag "Parentheticals" paren)
-                                 (const :tag "Transitions" trans)
-                                 (const :tag "Center Text" center)
-                                 (const :tag "Page Breaks" page-break)
-                                 (const :tag "Synopses" synopsis)
-                                 (const :tag "Notes" note)
-                                 (const :tag "Included Files" include)))
-  :group 'fountain-export)
-
-(define-obsolete-variable-alias 'fountain-export-include-elements-alist
-  'fountain-export-include-elements "2.4.0")
-
-(defcustom fountain-export-make-standalone
-  t
-  "If non-nil, export a standalone document.
-
-A standalone document is formatted with the export format's
-document template in `fountain-export-templates'.
-
-If nil, export snippet, which only formats each element. This is
-useful when exporting parts of a script for inclusion in another
-document."
-  :type 'boolean
-  :group 'fountain-export)
-
-(defcustom fountain-export-tmp-buffer-name
-  "*Fountain %s Export*"
-  "Name of export buffer when source buffer is not visiting a file.
-Passed to `format' with export format as single variable."
-  :type 'string
-  :group 'fountain-export)
-
-(defcustom fountain-export-default-command
-  'fountain-export-buffer-to-latex
-  "\\<fountain-mode-map>Default function to call with \\[fountain-export-default]."
-  :type '(radio (function-item fountain-export-buffer-to-latex)
-                (function-item fountain-export-buffer-to-html)
-                (function-item fountain-export-buffer-to-fdx)
-                (function-item fountain-export-buffer-to-fountain)
-                (function-item fountain-export-buffer-to-txt)
-                (function-item fountain-export-shell-command))
-  :group 'fountain-export)
-
-(make-obsolete-variable 'fountain-export-include-title-page
-  'fountain-export-include-elements "2.4.0")
-
-(defcustom fountain-export-page-size
-  'letter
-  "Paper size to use on export."
-  :type '(radio (const :tag "US Letter" letter)
-                (const :tag "A4" a4))
-  :group 'fountain-export)
-
-(defcustom fountain-export-font
-  "Courier"
-  "Font to use when exporting."
-  :type '(string :tag "Font")
-  :group 'fountain-export)
-
-(defcustom fountain-export-include-title-page
-  t
-  "If non-nil, include a title page in export."
-  :type 'boolean
-  :group 'fountain-export
-  :set (lambda (symbol value)
-         (set-default symbol value)
-         (dolist (buffer (buffer-list))
-           (with-current-buffer buffer
-             (when (eq major-mode 'fountain-mode)
-               (font-lock-refresh-defaults))))))
-
-(defcustom fountain-export-contact-align-right
-  nil
-  "If non-nil, align title page contact block on the right."
-  :type 'boolean
-  :group 'fountain-export)
-
-(defcustom fountain-export-number-first-page
-  nil
-  "If non-nil, add a page number to the first page.
-
-Traditionally, screenplays omit a page number on the first page."
-  :type 'boolean
-  :group 'fountain-export)
-
-(defcustom fountain-export-include-scene-numbers
-  nil
-  "If non-nil, include scene numbers in export."
-  :type 'boolean
-  :group 'fountain-export)
-
-(defcustom fountain-export-scene-heading-format
-  '(double-space)
-  "List of format options applied when exporting scene headings.
-Options are: bold, double-space, underline."
-  :type '(set (const :tag "Bold" bold)
-              (const :tag "Double-spaced" double-space)
-              (const :tag "Underlined" underline))
-  :group 'fountain-export)
-
-(defcustom fountain-export-more-dialog-string
-  "(MORE)"
-  "String to append to dialog when breaking across pages."
-  :type 'string
-  :group 'fountain-export)
-
-(defcustom fountain-export-shell-command
-  "afterwriting --source %s --pdf --overwrite"
-  "Shell command string to convert Fountain source to ouput.
-`%s' will be substituted with `buffer-file-name'"
-  :type 'string
-  :group 'fountain-export)
-
-(defcustom fountain-export-use-title-as-filename
-  nil
-  "If non-nil, use title metadata as export filename.
-
-This is useful if you are exporting to Fountain and need to
-specify a different filename."
-  :type 'boolean
-  :group 'fountain-export)
-
-(defvar fountain-export-formats
-  '((html
-     :tag "HTML"
-     :ext ".html"
-     :template fountain-export-html-template
-     :string-replace (("&" "&amp;")
-                      ("<" "&lt;")
-                      (">" "&gt;")
-                      ("\\\\\\*" "&#42;")
-                      ("\\*\\*\\*\\(.+?\\)\\*\\*\\*" "<strong><em>\\1</em></strong>")
-                      ("\\*\\*\\(.+?\\)\\*\\*" "<strong>\\1</strong>")
-                      ("\\*\\(.+?\\)\\*" "<em>\\1</em>")
-                      ("^~\s*\\(.+?\\)$" "<i>\\1</i>")
-                      ("_\\(.+?\\)_" "<span class=\"underline\">\\1</span>")
-                      ("\n\n+" "<br><br>")
-                      ("\n" "<br>"))
-     :eval-replace ((stylesheet fountain-export-html-stylesheet)
-                    (font fountain-export-font)
-                    (scene-heading-spacing
-                     (if (memq 'double-space fountain-export-scene-heading-format)
-                         "2em" "1em"))
-                    (title-page
-                     (if fountain-export-include-title-page
-                         fountain-export-html-title-page-template)))
-     :hook fountain-export-html-hook)
-    (tex
-     :tag "LaTeX"
-     :ext ".tex"
-     :template fountain-export-tex-template
-     :string-replace (("%" "\\\\%")
-                      ("&" "\\\\&")
-                      ("#" "\\\\#")
-                      ("\\$" "\\\\$")
-                      ("\\*\\*\\*\\(.+?\\)\\*\\*\\*" "\\\\textbf{\\\\emph{\\1}}")
-                      ("\\*\\*\\(.+?\\)\\*\\*" "\\\\textbf{\\1}")
-                      ("\\*\\(.+?\\)\\*" "\\\\emph{\\1}")
-                      ("^~\s*\\(.+?\\)$\\*\\*" "\\\\textit{\\1}")
-                      ("_\\(.+?\\)_" "\\\\uline{\\1}")
-                      ("\n[\s\t]*\n+" "\\\\par")
-                      ("\n" "\\\\protecting{\\\\\\\\}"))
-     :eval-replace ((font fountain-export-font)
-                    (scene-heading-spacing
-                     (if (memq 'double-space fountain-export-scene-heading-format)
-                         "true" "false"))
-                    (scene-heading-underline
-                     (if (memq 'underline fountain-export-scene-heading-format)
-                         "true" "false"))
-                    (scene-heading-bold
-                     (if (memq 'bold fountain-export-scene-heading-format)
-                         "true" "false"))
-                    (title-page
-                     (if fountain-export-include-title-page
-                         fountain-export-tex-title-page-template))
-                    (title-contact-align
-                     (if fountain-export-contact-align-right
-                         "true" "false"))
-                    (number-first-page
-                     (if fountain-export-number-first-page
-                         "true" "false"))
-                    (include-scene-numbers
-                     (if fountain-export-include-scene-numbers
-                         "true" "false")))
-     :hook fountain-export-tex-hook)
-    (fdx
-     :tag "Final Draft"
-     :ext ".fdx"
-     :template fountain-export-fdx-template
-     :string-replace (("&" "&#38;")
-                      ("<" "&#60;")
-                      (">" "&#62;")
-                      ("\"" "&#34;")
-                      ("'" "&#39;")
-                      ("\\\\_" "&#96;")
-                      ("\\\\\\*" "&#42;")
-                      ("_\\*\\*\\*\\(.+?\\)\\*\\*\\*_" "</Text><Text Style=\"Bold+Underline+Italic\">\\1</Text><Text>")
-                      ("\\*\\*\\*\\(.+?\\)\\*\\*\\*" "</Text><Text Style=\"Bold+Italic\">\\1</Text><Text>")
-                      ("_\\*\\*\\(.+?\\)\\*\\*_" "</Text><Text Style=\"Bold+Underline\">\\1</Text><Text>")
-                      ("_\\*\\(.+?\\)\\*_" "</Text><Text Style=\"Underline+Italic\">\\1</Text><Text>")
-                      ("\\*\\*\\(.+?\\)\\*\\*" "</Text><Text Style=\"Bold\">\\1</Text><Text>")
-                      ("\\*\\(.+?\\)\\*" "</Text><Text Style=\"Italic\">\\1</Text><Text>")
-                      ("^~\s*\\(.+?\\)$" "</Text><Text Style=\"Italic\">\\1</Text><Text>")
-                      ("_\\(.+?\\)_" "</Text><Text Style=\"Underline\">\\1</Text><Text>")
-                      ("\n\n+" "\n\n"))
-     :cond-replace ((t
-                     (starts-new-page
-                      (t "Yes") (nil "No"))))
-     :eval-replace ((title-page
-                     (if fountain-export-include-title-page
-                         fountain-export-fdx-title-page-template)))
-     :hook fountain-export-fdx-hook)
-    (fountain
-     :tag "Fountain"
-     :ext ".fountain"
-     :template fountain-export-fountain-template
-     :cond-replace ((scene-heading
-                     (forced (t ".")))
-                    (character
-                     (dual (right " ^"))
-                     (forced (t "@")))
-                    (trans
-                     (forced (t "> ")))
-                    (action
-                     (forced (t "!")))
-                    (section-heading
-                     (level (1 "#")
-                            (2 "##")
-                            (3 "###")
-                            (4 "####")
-                            (5 "#####"))))
-     :eval-replace ((title-page
-                     (if fountain-export-include-title-page
-                         fountain-export-fountain-title-page-template))
-                    (scene-heading-spacing
-                     (if (memq 'double-space fountain-export-scene-heading-format)
-                         "\n")))
-     :hook fountain-export-fountain-hook)
-    (txt
-     :tag "plaintext"
-     :ext ".txt"
-     :fill t
-     :template fountain-export-txt-template
-     :eval-replace ((scene-heading-spacing
-                     (if (memq 'double-space fountain-export-scene-heading-format)
-                         "\n"))
-                    (title-page
-                     (if fountain-export-include-title-page
-                         fountain-export-txt-title-page-template)))
-     :hook fountain-export-txt-hook))
-  "Association list of export formats and their properties.
-Takes the form:
-
-    ((FORMAT KEYWORD PROPERTY)
-      ...)")
+         ;; Don't call fountain-count-pages-maybe while in the middle of
+         ;; loading this file!
+         (when (featurep 'fountain-mode)
+           (dolist (buffer (buffer-list))
+             (with-current-buffer buffer
+               (fountain-count-pages-maybe value))))))
 
 (defvar fountain-elements
   '((section-heading
@@ -2490,6 +1458,994 @@
 
     (ELEMENT KEYWORD PROPERTY)")
 
+(defvar fountain-export-page-size)
+(defvar fountain-export-more-dialog-string)
+
+(defun fountain-goto-page-break-point ()
+  "Move point to appropriate place to break a page.
+This is usually before point, but may be after if only skipping
+over whitespace."
+  ;; FIXME: currently does not account for elements not included in
+  ;; `fountain-export-include-elements' for current format. This will throw page
+  ;; off page counting in many cases.
+  (when (looking-at "\n[\n\s\t]*\n") (goto-char (match-end 0)))
+  (let ((element (fountain-get-element)))
+    (cond
+     ;; If we're are a section heading, scene heading or character, we can
+     ;; safely break before.
+     ((memq element '(section-heading scene-heading character))
+      (beginning-of-line))
+     ;; If we're at a parenthetical, check if the previous line is a character.
+     ;; and if so call recursively on that element.
+     ((eq element 'paren)
+      (beginning-of-line)
+      (let ((x (point)))
+        (backward-char)
+        (if (fountain-match-character)
+            (progn
+              (beginning-of-line)
+              (fountain-goto-page-break-point))
+          ;; Otherwise parenthetical is mid-dialogue, so get character name
+          ;; and break at this element.
+          (goto-char x))))
+     ;; If we're at dialogue, skip over spaces then go to the beginning of the
+     ;; current sentence.
+     ((eq element 'lines)
+      (skip-chars-forward "\s\t")
+      (if (not (looking-back (sentence-end)
+                             (save-excursion
+                               (fountain-forward-character 0)
+                               (point))))
+          (forward-sentence -1)
+        ;; This may move to character element, or back within dialogue. If
+        ;; previous line is a character or parenthetical, call recursively on
+        ;; that element. Otherwise, get character name and break page here.
+        (let ((x (point)))
+          (backward-char)
+          (if (or (fountain-match-character)
+                  (fountain-match-paren))
+              (fountain-goto-page-break-point)
+            (goto-char x)))))
+     ;; If we're at a transition or center text, skip backwards to previous
+     ;; element and call recursively on that element.
+     ((memq element '(trans center))
+      (skip-chars-backward "\n\r\s\t")
+      (beginning-of-line)
+      (fountain-goto-page-break-point))
+     ;; If we're at action, skip over spaces then go to the beginning of the
+     ;; current sentence.
+     ((eq element 'action)
+      (skip-chars-forward "\s\t")
+      (unless (or (bolp)
+                  (looking-back (sentence-end) nil))
+        (forward-sentence -1))
+      ;; Then, try to skip back to the previous element. If it is a scene
+      ;; heading, call recursively on that element. Otherwise, break page here.
+      (let ((x (point)))
+        (skip-chars-backward "\n\r\s\t")
+        (beginning-of-line)
+        (if (fountain-match-scene-heading)
+            (fountain-goto-page-break-point)
+          (goto-char x)))))))
+
+(defun fountain-forward-page (&optional n export-elements)
+  "Move point forward by an approximate page.
+
+Moves forward from point, which is unlikely to correspond to
+final exported pages and so probably should not be used
+interactively.
+
+To considerably speed up this function, supply EXPORT-ELEMENTS
+with `fountain-get-export-elements'."
+  (let ((skip-whitespace-fun
+         (lambda ()
+           (when (looking-at "[\n\s\t]*\n") (goto-char (match-end 0))))))
+    (unless n (setq n 1))
+    (while (< 0 n)
+      ;; Pages don't begin with blank space, so skip over any at point.
+      (funcall skip-whitespace-fun)
+      ;; If we're at a page break, move to its end and skip over whitespace.
+      (when (fountain-match-page-break)
+        (goto-char (match-end 0))
+        (funcall skip-whitespace-fun))
+      ;; Start counting lines.
+      (let ((line-count 0))
+        ;; Begin the main loop, which only halts if we reach the end of buffer,
+        ;; a forced page break, or after the maximum lines in a page.
+        (while (and (< line-count (cdr (assq fountain-export-page-size
+                                             fountain-pages-max-lines)))
+                    (not (eobp))
+                    (not (fountain-match-page-break)))
+          (cond
+           ;; If we're at the end of a line (but not also the beginning, i.e.
+           ;; not a blank line) then move forward a line and increment
+           ;; line-count.
+           ((and (eolp) (not (bolp)))
+            (forward-line)
+            (setq line-count (1+ line-count)))
+           ;; If we're looking at newline, skip over it and any whitespace and
+           ;; increment line-count.
+           ((looking-at "[\n\s\t]*\n")
+            (goto-char (match-end 0))
+            (setq line-count (1+ line-count)))
+           ;; We are at an element. Find what kind of element. If it is not
+           ;; included in export, skip over without incrementing line-count
+           ;; (implement with block bounds). Get the line width.
+           (t
+            (let ((element (fountain-get-element)))
+              (if (memq element (or export-elements
+                                    (fountain-get-export-elements)))
+                  (progn
+                    (fountain-move-to-fill-width element)
+                    (setq line-count (1+ line-count)))
+                ;; Element is not exported, so skip it without incrementing
+                ;; line-count.
+                (end-of-line)
+                (funcall skip-whitespace-fun)))))))
+      ;; We are not at the furthest point in a page. Skip over any remaining
+      ;; whitespace, then go back to page-break point.
+      (skip-chars-forward "\n\s\t")
+      (fountain-goto-page-break-point)
+      (setq n (1- n)))))
+
+(defun fountain-move-to-fill-width (element)
+  "Move point to column of ELEMENT fill limit suitable for breaking line.
+Skip over comments."
+  (let ((fill-width
+         (cdr (symbol-value
+               (plist-get (cdr (assq element fountain-elements))
+                          :fill)))))
+    (let ((i 0))
+      (while (and (< i fill-width) (not (eolp)))
+        (cond ((= (syntax-class (syntax-after (point))) 0)
+               (forward-char 1)
+               (setq i (1+ i)))
+              ((forward-comment 1))
+              (t
+               (forward-char 1)
+               (setq i (1+ i))))))
+    (skip-chars-forward "\s\t")
+    (when (eolp) (forward-line))
+    (unless (bolp) (fill-move-to-break-point (line-beginning-position)))))
+
+(defun fountain-insert-page-break (&optional string)
+  "Insert a page break at appropriate place preceding point.
+STRING is an optional page number string to force the page
+number."
+  (interactive "sPage number (RET for none): ")
+  ;; Save a marker where we are.
+  (let ((x (point-marker))
+        (page-break
+         (concat "===" (when (< 0 (string-width string))
+                         (concat "\s" string "\s==="))))
+        element)
+    ;; Move point to appropriate place to break page.
+    (fountain-goto-page-break-point)
+    (setq element (fountain-get-element))
+    ;; At this point, element can only be: section-heading, scene-heading,
+    ;; character, action, paren or lines. Only paren and lines require special
+    ;; treatment.
+    (if (memq element '(lines paren))
+        (let ((name (fountain-get-character -1)))
+          (insert (concat
+                   fountain-export-more-dialog-string "\n\n"
+                   page-break "\n\n"
+                   name "\s" fountain-continued-dialog-string "\n")))
+      ;; Otherwise, insert the page break where we are. If the preceding element
+      ;; is a page break, only replace the page number, otherwise, insert the
+      ;; page break.
+      (if (save-excursion
+            (save-restriction
+              (widen)
+              (skip-chars-backward "\n\r\s\t")
+              (fountain-match-page-break)))
+          (replace-match page-break t t)
+        (delete-horizontal-space)
+        (unless (bolp) (insert "\n\n"))
+        (insert-before-markers page-break "\n\n")))
+    ;; Return to where we were.
+    (goto-char x)))
+
+(defun fountain-get-page-count ()
+  (let ((x (point))
+        (total 0)
+        (current 0)
+        (end (point-max))
+        (export-elements (fountain-get-export-elements))
+        found)
+    (save-excursion
+      (save-restriction
+        (when fountain-pages-ignore-narrowing (widen))
+        (goto-char (point-min))
+        (while (< (point) end)
+          (fountain-forward-page 1 export-elements)
+          (setq total (1+ total))
+          (when (and (not found) (<= x (point)))
+            (setq current total found t)))
+        (cons current total)))))
+
+(defun fountain-count-pages (&optional interactive)
+  "Return the approximate current page of total pages in current buffer.
+
+If called interactively, sets INTERACTIVE as non-nil
+unconditionally and prints a message in the echo area."
+  (interactive "p")
+  (fountain-pages-update-mode-line)
+  (redisplay)
+  (let ((pages (fountain-get-page-count)))
+    (fountain-pages-update-mode-line (car pages) (cdr pages))
+    (when interactive
+      (message "Page %d of %d" (car pages) (cdr pages)))))
+
+(defun fountain-pages-update-mode-line (&optional current total)
+  (setq fountain-page-count-string
+        (if fountain-pages-show-in-mode-line
+            (if (and current total)
+                (format "[%d/%d] " current total)
+              "[-/-] ")
+          nil))
+  (force-mode-line-update))
+
+(defun fountain-count-pages-maybe (&optional force)
+  (when (derived-mode-p 'fountain-mode)
+    (while-no-input
+      (redisplay)
+      (cond (force
+             (fountain-count-pages))
+            ((eq fountain-pages-show-in-mode-line 'timer)
+             (fountain-count-pages))
+            ((and fountain-page-count-string
+                  (not fountain-pages-show-in-mode-line))
+             (fountain-pages-update-mode-line))))))
+
+(defun fountain-init-mode-line ()
+  (let ((tail (cdr (memq 'mode-line-modes mode-line-format))))
+    (setq mode-line-format
+          (append
+           (butlast mode-line-format (length tail))
+           (cons 'fountain-page-count-string tail)))))
+
+(defun fountain-cancel-page-count-timer ()
+  (when (timerp fountain-page-count-timer)
+    (cancel-timer fountain-page-count-timer))
+  (setq fountain-page-count-timer nil))
+
+(defun fountain-restart-page-count-timer ()
+  (fountain-cancel-page-count-timer)
+  (setq fountain-page-count-timer
+        ;; FIXME: `fountain-count-pages-maybe' only operates in the "current"
+        ;; buffer, but that will be the buffer that happens to be current when
+        ;; the timer is run.
+        (run-with-idle-timer fountain-pages-count-delay t
+                             #'fountain-count-pages-maybe)))
+
+
+;;; Templating
+
+(defconst fountain-template-regexp
+  "{{[\s\t\n]*\\(?1:[.-a-z0-9]+\\)\\(?::[\s\t\n]*\\(?2:[^{}]+?\\)\\)?[\s\t\n]*}}"
+  "Regular expression for matching template keys.")
+
+(defun fountain-match-template ()
+  "Match template key if point is at template, nil otherwise."
+  (save-excursion
+    (save-restriction
+      (widen)
+      (or (looking-at fountain-template-regexp)
+          (let ((x (point)))
+            (and (re-search-backward "{{" nil t)
+                 (looking-at fountain-template-regexp)
+                 (< x (match-end 0))))))))
+
+(defun fountain-find-included-file (&optional no-select)
+  "Find included file at point.
+
+Optional argument NO-SELECT will find file without selecting
+window."
+  (interactive)
+  (when (and (fountain-match-template)
+             (string-match-p "include" (match-string 1)))
+    (let ((file (expand-file-name (match-string 2))))
+      (if no-select
+          (find-file-noselect file)
+        (find-file file)))))
+
+(defun fountain-include-replace-in-region (start end &optional delete)
+  "Replace inclusions between START and END with their file contents.
+
+If optional argument DELETE is non-nil (if prefix with \\[universal-argument]
+when called interactively), delete instead.
+
+If specified file is missing or otherwise not readable, replace
+with empty string."
+  (interactive "*r\nP")
+  (save-excursion
+    (save-restriction
+      (widen)
+      (goto-char end)
+      (setq end (point-marker))
+      (goto-char start)
+      (while (re-search-forward fountain-template-regexp end t)
+        (when (string-match-p "include" (match-string 1))
+          (if delete
+              (delete-region (match-beginning 0) (match-end 0))
+            (replace-match
+              (let ((file (match-string 2)))
+                (if (file-readable-p file)
+                    (save-match-data
+                      (with-current-buffer
+                          (find-file-noselect (expand-file-name (match-string 2)))
+                        (save-restriction
+                          (widen)
+                          (buffer-substring-no-properties (point-min) (point-max)))))
+                  ""))
+              t t))))
+      (set-marker end nil))))
+
+
+;;; Parsing
+
+(defun fountain-get-character (&optional n limit)
+  "Return Nth next character (or Nth previous if N is negative).
+
+If N is non-nil, return Nth next character or Nth previous
+character if N is negative, otherwise return nil. If N is nil or
+0, return character at point, otherwise return nil.
+
+If LIMIT is 'scene, halt at next scene heading. If LIMIT is
+'dialog, halt at next non-dialog element."
+  (unless n (setq n 0))
+  (save-excursion
+    (save-restriction
+      (widen)
+      (fountain-forward-character n limit)
+      (when (fountain-match-character)
+        (match-string-no-properties 4)))))
+
+(defun fountain-read-metadata ()
+  "Read metadata of current buffer and return as a property list.
+
+Key string is slugified using `fountain-slugify', and interned.
+Value string remains a string. e.g.
+
+    Draft date: 2015-12-25 -> (draft-date \"2015-12-25\")"
+  (save-excursion
+    (save-restriction
+      (widen)
+      (goto-char (point-min))
+      (let (list)
+        (while (fountain-match-metadata)
+          (let ((key (match-string 2))
+                (value (match-string-no-properties 3)))
+            (forward-line)
+            (while (and (fountain-match-metadata)
+                        (null (match-string 2)))
+              (setq value
+                    (concat value (when value "\n")
+                            (match-string-no-properties 3)))
+              (forward-line))
+            (setq list
+                  (append list (list (intern (fountain-slugify key))
+                                     value)))))
+        list))))
+
+(defun fountain-dual-dialog (&optional pos)
+  "Non-nil if point or POS is within dual dialogue.
+Returns \"right\" if within right-side dual dialogue, \"left\" if
+within left-side dual dialogue, and nil otherwise."
+  (save-excursion
+    (save-match-data
+      (save-restriction
+        (widen)
+        (when pos (goto-char pos))
+        (cond ((progn (fountain-forward-character 0 'dialog)
+                      (and (fountain-match-character)
+                           (stringp (match-string 5))))
+               'right)
+              ((progn (fountain-forward-character 1 'dialog)
+                      (and (fountain-match-character)
+                           (stringp (match-string 5))))
+               'left))))))
+
+(defun fountain-starts-new-page () ; FIXME: implement LIMIT
+  (save-excursion
+    (save-match-data
+      (save-restriction
+        (widen)
+        (beginning-of-line)
+        (skip-chars-backward "\n\r\s\t")
+        (fountain-match-page-break)))))
+
+(defun fountain-parse-section (match-data &optional export-elements job)
+  "Return an element list for matched section heading."
+  (set-match-data match-data)
+  (let* ((beg (match-beginning 0))
+         (starts-new-page (fountain-starts-new-page))
+         (section-heading
+          (list 'section-heading
+                (list 'begin (match-beginning 0)
+                      'end (match-end 0)
+                      'level (save-excursion
+                               (goto-char (match-beginning 0))
+                               (funcall outline-level))
+                      'export (when (memq 'section-heading export-elements) t))
+                (match-string-no-properties 3)))
+         (end (save-excursion (outline-end-of-subtree) (point)))
+         content)
+    (goto-char (plist-get (nth 1 section-heading) 'end))
+    (setq content
+          (fountain-parse-region (point) end export-elements job))
+    (list 'section
+          (list 'begin beg
+                'end end
+                'starts-new-page starts-new-page
+                'export t)
+          (cons section-heading content))))
+
+(defun fountain-parse-scene (match-data &optional export-elements job)
+  "Return an element list for matched scene heading at point.
+Includes child elements."
+  (set-match-data match-data)
+  (let* ((beg (match-beginning 0))
+         (starts-new-page (fountain-starts-new-page))
+         (scene-number
+          (save-excursion
+            (save-match-data
+              (goto-char (match-beginning 0))
+              (fountain-scene-number-to-string
+               (fountain-get-scene-number 0)))))
+         (scene-heading
+          (list 'scene-heading
+                (list 'begin (match-beginning 0)
+                      'end (match-end 0)
+                      'scene-number scene-number
+                      'forced (stringp (match-string 1))
+                      'export (when (memq 'scene-heading export-elements) t)
+                      'starts-new-page starts-new-page)
+                (match-string-no-properties 2)))
+         (end (save-excursion (outline-end-of-subtree) (point)))
+         content)
+    (goto-char (plist-get (nth 1 scene-heading) 'end))
+    (setq content
+          (fountain-parse-region (point) end export-elements job))
+    (list 'scene
+          (list 'begin beg
+                'end end
+                'scene-number scene-number
+                'starts-new-page starts-new-page
+                'export t)
+          (cons scene-heading content))))
+
+(defun fountain-parse-dialog (match-data &optional export-elements job)
+  (set-match-data match-data)
+  (let* ((beg (match-beginning 0))
+         (starts-new-page (fountain-starts-new-page))
+         (dual (fountain-dual-dialog))
+         (character
+          (list 'character
+                (list 'begin (match-beginning 0)
+                      'end (match-end 0)
+                      'forced (stringp (match-string 2))
+                      'export (when (memq 'character export-elements) t)
+                      'starts-new-page (unless (eq dual 'left) starts-new-page))
+                (match-string-no-properties 3)))
+         (end
+          (save-excursion
+            (fountain-forward-character 1 'dialog)
+            (skip-chars-backward "\n\r\s\t")
+            (point)))
+         first-dialog)
+    (goto-char (plist-get (nth 1 character) 'end))
+    ;; Parse the first dialogue tree, which may be the only dialogue tree.
+    (setq first-dialog
+          (list 'dialog
+                (list 'begin beg
+                      'end end
+                      'dual dual
+                      'export (when (or (memq 'character export-elements)
+                                        (memq 'lines export-elements)
+                                        (memq 'paren export-elements))
+                                t))
+                (cons character
+                      (fountain-parse-region (point) end export-elements job))))
+    ;; If at the first (left) character of dual dialogue, parse a dual-dialogue
+    ;; tree, containing dialogue trees.
+    (if (eq dual 'left)
+        ;; Find the end of the dual-dialogue.
+        (let ((end
+               (save-excursion
+                 (while (fountain-dual-dialog)
+                   (fountain-forward-character 1 'dialog))
+                 (skip-chars-backward "\n\r\s\t")
+                 (point))))
+          ;; Return the dual-dialogue tree.
+          (list 'dual-dialog
+                (list 'begin beg
+                      'end end
+                      'starts-new-page starts-new-page
+                      'export (when (or (memq 'character export-elements)
+                                        (memq 'lines export-elements)
+                                        (memq 'paren export-elements))
+                                t))
+                ;; Add the first dialogue block to the head of the dual-dialogue
+                ;; tree.
+                (cons first-dialog
+                      ;; Parse the containing region.
+                      (fountain-parse-region
+                       (plist-get (nth 1 first-dialog) 'end)
+                       end export-elements job))))
+      ;; Otherwise, return the first dialogue tree.
+      first-dialog)))
+
+(defun fountain-parse-lines (match-data &optional export-elements _job)
+  "Return an element list for matched dialogue."
+  (set-match-data match-data)
+  (let ((beg (match-beginning 0))
+        (end (match-end 0)))
+    (list 'lines
+          (list 'begin beg
+                'end end
+                'export (when (memq 'lines export-elements) t))
+          (match-string-no-properties 1))))
+
+(defun fountain-parse-paren (match-data &optional export-elements _job)
+  "Return an element list for matched parenthetical."
+  (set-match-data match-data)
+  (list 'paren
+        (list 'begin (match-beginning 0)
+              'end (match-end 0)
+              'export (when (memq 'paren export-elements) t))
+        (match-string-no-properties 1)))
+
+(defun fountain-parse-trans (match-data &optional export-elements _job)
+  "Return an element list for matched transition."
+  (set-match-data match-data)
+  (list 'trans
+        (list 'begin (match-beginning 0)
+              'end (match-end 0)
+              'forced (stringp (match-string 1))
+              'export (when (memq 'trans export-elements) t)
+              'starts-new-page (fountain-starts-new-page))
+        (match-string-no-properties 2)))
+
+(defun fountain-parse-center (match-data &optional export-elements _job)
+  "Return an element list for matched center text."
+  (set-match-data match-data)
+  (list 'center
+        (list 'begin (match-beginning 0)
+              'end (match-end 0)
+              'export (when (memq 'center export-elements) t)
+              'starts-new-page (fountain-starts-new-page))
+        (match-string-no-properties 3)))
+
+(defun fountain-parse-page-break (match-data &optional export-elements _job)
+  "Return an element list for matched page break."
+  (set-match-data match-data)
+  (list 'page-break
+        (list 'begin (match-beginning 0)
+              'end (match-end 0)
+              'export (when (memq 'page-break export-elements) t))
+        (match-string-no-properties 2)))
+
+(defun fountain-parse-synopsis (match-data &optional export-elements _job)
+  "Return an element list for matched synopsis."
+  (set-match-data match-data)
+  (list 'synopsis
+        (list 'begin (match-beginning 0)
+              'end (match-end 0)
+              'export (when (memq 'synopsis export-elements) t)
+              'starts-new-page (fountain-starts-new-page))
+        (match-string-no-properties 3)))
+
+(defun fountain-parse-note (match-data &optional export-elements _job)
+  "Return an element list for matched note."
+  (set-match-data match-data)
+  (list 'note
+        (list 'begin (match-beginning 0)
+              'end (match-end 0)
+              'export (when (memq 'note export-elements) t)
+              'starts-new-page (fountain-starts-new-page))
+        (match-string-no-properties 2)))
+
+(defun fountain-parse-action (match-data &optional export-elements _job)
+  "Return an element list for matched action."
+  (set-match-data match-data)
+  (let ((bounds (fountain-get-block-bounds))
+        begin end string)
+    (setq begin (car bounds))
+    (save-excursion
+      (goto-char (cdr bounds))
+      (skip-chars-backward "\n\s\t")
+      (setq end (point)))
+    (setq string (buffer-substring-no-properties begin end)
+          string (replace-regexp-in-string "^!" "" string))
+    (list 'action
+          (list 'begin begin
+                'end end
+                'forced (stringp (match-string 1))
+                'export (when (memq 'action export-elements) t)
+                'starts-new-page (fountain-starts-new-page))
+          string)))
+
+(defun fountain-parse-element (&optional export-elements job)
+  "Call appropropriate element parsing function for matched element at point."
+  (let ((parser (plist-get (cdr (assq (fountain-get-element)
+                                      fountain-elements))
+                           :parser)))
+    (when parser (funcall parser (match-data) export-elements job))))
+
+(defun fountain-parse-region (start end export-elements job)
+  "Return a list of parsed element lists in region between START and END."
+  (goto-char start)
+  (setq end (min end (point-max)))
+  (let (list)
+    (while (< (point) end)
+      (skip-chars-forward "\n\r\s\t")
+      (beginning-of-line)
+      (when (< (point) end)
+        (let ((element (fountain-parse-element export-elements job)))
+          (push element list)
+          (goto-char (plist-get (nth 1 element) 'end))))
+      (when job (progress-reporter-update job)))
+    (reverse list)))
+
+(defun fountain-prep-and-parse-region (start end)
+  "Prepare and parse region between START and END."
+  (let ((buffer (current-buffer))
+        (export-elements (fountain-get-export-elements))
+        (job (make-progress-reporter "Parsing...")))
+    (prog1
+        (with-temp-buffer
+          (fountain-init-vars)
+          (insert-buffer-substring buffer start end)
+          (fountain-include-replace-in-region
+           (point-min) (point-max) (not (memq 'include export-elements)))
+          (fountain-delete-comments-in-region (point-min) (point-max))
+          ;; Delete metadata.
+          (goto-char (point-min))
+          (while (fountain-match-metadata)
+            (forward-line))
+          (delete-region (point-min) (point))
+          (fountain-parse-region (point-min) (point-max) export-elements job))
+      (progress-reporter-done job))))
+
+
+;;; Filling
+
+(defgroup fountain-fill ()
+  "Options for filling elements.
+
+Filling elements is used in exporting to plaintext and
+PostScript, and in calculating page length for page locking."
+  :prefix "fountain-fill-"
+  :group 'fountain-export)
+
+(defcustom fountain-fill-section-heading
+  '(0 . 61)
+  "Cons cell of integers for indenting and filling section headings.
+The car sets `left-margin' and cdr `fill-column'."
+  :type '(cons (integer :tag "Indent")
+               (integer :tag "Width")))
+
+(defcustom fountain-fill-scene-heading
+  '(0 . 61)
+  "Cons cell of integers for indenting and filling scene headings.
+The car sets `left-margin' and cdr `fill-column'."
+  :type '(cons (integer :tag "Indent")
+               (integer :tag "Width")))
+
+(defcustom fountain-fill-action
+  '(0 . 61)
+  "Cons cell of integers for indenting and filling action.
+The car sets `left-margin' and cdr `fill-column'."
+  :type '(cons (integer :tag "Indent")
+               (integer :tag "Width")))
+
+(defcustom fountain-fill-character
+  '(20 . 38)
+  "Cons cell of integers for indenting and filling character.
+The car sets `left-margin' and cdr `fill-column'."
+  :type '(cons (integer :tag "Indent")
+               (integer :tag "Width")))
+
+(defcustom fountain-fill-paren
+  '(15 . 26)
+  "Cons cell of integers for indenting and filling parenthetical.
+The car sets `left-margin' and cdr `fill-column'."
+  :type '(cons (integer :tag "Indent")
+               (integer :tag "Width")))
+
+(defcustom fountain-fill-dialog
+  '(10 . 35)
+  "Cons cell of integers for indenting and filling dialogue.
+The car sets `left-margin' and cdr `fill-column'."
+  :type '(cons (integer :tag "Indent")
+               (integer :tag "Width")))
+
+(defcustom fountain-fill-trans
+  '(42 . 16)
+  "Cons cell of integers for indenting and filling transition.
+The car sets `left-margin' and cdr `fill-column'."
+  :type '(cons (integer :tag "Indent")
+               (integer :tag "Width")))
+
+(defcustom fountain-fill-synopsis
+  '(0 . 61)
+  "Cons cell of integers for indenting and filling synopses.
+The car sets `left-margin' and cdr `fill-column'."
+  :type '(cons (integer :tag "Indent")
+               (integer :tag "Width")))
+
+(defcustom fountain-fill-note
+  '(0 . 61)
+  "Cons cell of integers for indenting and filling notes.
+The car sets `left-margin' and cdr `fill-column'."
+  :type '(cons (integer :tag "Indent")
+               (integer :tag "Width")))
+
+
+;;; Exporting
+
+(defgroup fountain-export ()
+  "Options for exporting Fountain files."
+  :prefix "fountain-export-"
+  :group 'fountain)
+
+(defcustom fountain-export-include-elements
+  '(("screenplay" scene-heading action character lines paren trans center page-break include)
+    ("teleplay" section-heading scene-heading action character lines paren trans center page-break include)
+    ("stageplay" section-heading scene-heading action character lines paren trans center page-break include))
+  "Association list of elements to include when exporting.
+Note that comments (boneyard) are never included."
+  :type '(alist :key-type (string :tag "Format")
+                :value-type (set :tag "Elements"
+                                 (const :tag "Section Headings" section-heading)
+                                 (const :tag "Scene Headings" scene-heading)
+                                 (const :tag "Action" action)
+                                 (const :tag "Character Names" character)
+                                 (const :tag "Dialogue" lines)
+                                 (const :tag "Parentheticals" paren)
+                                 (const :tag "Transitions" trans)
+                                 (const :tag "Center Text" center)
+                                 (const :tag "Page Breaks" page-break)
+                                 (const :tag "Synopses" synopsis)
+                                 (const :tag "Notes" note)
+                                 (const :tag "Included Files" include))))
+
+(defcustom fountain-export-make-standalone
+  t
+  "If non-nil, export a standalone document.
+
+A standalone document is formatted with the export format's
+document template in `fountain-export-templates'.
+
+If nil, export snippet, which only formats each element. This is
+useful when exporting parts of a script for inclusion in another
+document."
+  :type 'boolean
+  :safe 'booleanp)
+
+(defcustom fountain-export-tmp-buffer-name
+  "*Fountain %s Export*"
+  "Name of export buffer when source buffer is not visiting a file.
+Passed to `format' with export format as single variable."
+  :type 'string
+  :safe 'stringp)
+
+(define-obsolete-variable-alias 'fountain-export-default-command
+  'fountain-export-default-function "fountain-mode-2.7.0")
+(defcustom fountain-export-default-function
+  #'fountain-export-buffer-to-latex
+  "\\<fountain-mode-map>Default function to call with \\[fountain-export-default]."
+  :type '(radio (function-item fountain-export-buffer-to-latex)
+                (function-item fountain-export-buffer-to-html)
+                (function-item fountain-export-buffer-to-fdx)
+                (function-item fountain-export-buffer-to-fountain)
+                (function-item fountain-export-buffer-to-txt)
+                (function-item fountain-export-shell-command)))
+
+(defcustom fountain-export-page-size
+  'letter
+  "Paper size to use on export."
+  :type '(radio (const :tag "US Letter" letter)
+                (const :tag "A4" a4)))
+
+(defcustom fountain-export-font
+  "Courier"
+  "Font to use when exporting."
+  :type '(string :tag "Font")
+  :safe 'stringp)
+
+(defcustom fountain-export-include-title-page
+  t
+  "If non-nil, include a title page in export."
+  :type 'boolean
+  :safe 'booleanp
+  :set #'fountain--set-and-refresh-all-font-lock)
+
+(defcustom fountain-export-contact-align-right
+  nil
+  "If non-nil, align title page contact block on the right."
+  :type 'boolean
+  :safe 'booleanp)
+
+(defcustom fountain-export-number-first-page
+  nil
+  "If non-nil, add a page number to the first page.
+
+Traditionally, screenplays omit a page number on the first page."
+  :type 'boolean
+  :safe 'booleanp)
+
+(defcustom fountain-export-include-scene-numbers
+  nil
+  "If non-nil, include scene numbers in export."
+  :type 'boolean
+  :safe 'booleanp)
+
+(defcustom fountain-export-scene-heading-format
+  '(double-space)
+  "List of format options applied when exporting scene headings.
+Options are: bold, double-space, underline."
+  :type '(set (const :tag "Bold" bold)
+              (const :tag "Double-spaced" double-space)
+              (const :tag "Underlined" underline)))
+
+(defcustom fountain-export-more-dialog-string
+  "(MORE)"
+  "String to append to dialog when breaking across pages."
+  :type 'string
+  :safe 'stringp)
+
+(defcustom fountain-export-shell-command
+  "afterwriting --source %s --pdf --overwrite"
+  "Shell command string to convert Fountain source to ouput.
+`%s' will be substituted with `buffer-file-name'"
+  :type 'string)
+
+(defcustom fountain-export-use-title-as-filename
+  nil
+  "If non-nil, use title metadata as export filename.
+
+This is useful if you are exporting to Fountain and need to
+specify a different filename."
+  :type 'boolean
+  :safe 'booleanp)
+
+(defvar fountain-export-formats
+  '((html
+     :tag "HTML"
+     :ext ".html"
+     :template fountain-export-html-template
+     :string-replace (("&" "&amp;")
+                      ("<" "&lt;")
+                      (">" "&gt;")
+                      ("\\\\\\*" "&#42;")
+                      ("\\*\\*\\*\\(.+?\\)\\*\\*\\*" "<strong><em>\\1</em></strong>")
+                      ("\\*\\*\\(.+?\\)\\*\\*" "<strong>\\1</strong>")
+                      ("\\*\\(.+?\\)\\*" "<em>\\1</em>")
+                      ("^~\s*\\(.+?\\)$" "<i>\\1</i>")
+                      ("_\\(.+?\\)_" "<span class=\"underline\">\\1</span>")
+                      ("\n\n+" "<br><br>")
+                      ("\n" "<br>"))
+     :eval-replace ((stylesheet fountain-export-html-stylesheet)
+                    (font fountain-export-font)
+                    (scene-heading-spacing
+                     (if (memq 'double-space fountain-export-scene-heading-format)
+                         "2em" "1em"))
+                    (title-page
+                     (when fountain-export-include-title-page
+                       fountain-export-html-title-page-template)))
+     :hook fountain-export-html-hook)
+    (tex
+     :tag "LaTeX"
+     :ext ".tex"
+     :template fountain-export-tex-template
+     :string-replace (("%" "\\\\%")
+                      ("&" "\\\\&")
+                      ("#" "\\\\#")
+                      ("\\$" "\\\\$")
+                      ("\\*\\*\\*\\(.+?\\)\\*\\*\\*" "\\\\textbf{\\\\emph{\\1}}")
+                      ("\\*\\*\\(.+?\\)\\*\\*" "\\\\textbf{\\1}")
+                      ("\\*\\(.+?\\)\\*" "\\\\emph{\\1}")
+                      ("^~\s*\\(.+?\\)$\\*\\*" "\\\\textit{\\1}")
+                      ("_\\(.+?\\)_" "\\\\uline{\\1}")
+                      ("\n[\s\t]*\n+" "\\\\par")
+                      ("\n" "\\\\protecting{\\\\\\\\}"))
+     :eval-replace ((font fountain-export-font)
+                    (scene-heading-spacing
+                     (if (memq 'double-space fountain-export-scene-heading-format)
+                         "true" "false"))
+                    (scene-heading-underline
+                     (if (memq 'underline fountain-export-scene-heading-format)
+                         "true" "false"))
+                    (scene-heading-bold
+                     (if (memq 'bold fountain-export-scene-heading-format)
+                         "true" "false"))
+                    (title-page
+                     (when fountain-export-include-title-page
+                       fountain-export-tex-title-page-template))
+                    (title-contact-align
+                     (if fountain-export-contact-align-right
+                         "true" "false"))
+                    (number-first-page
+                     (if fountain-export-number-first-page
+                         "true" "false"))
+                    (include-scene-numbers
+                     (if fountain-export-include-scene-numbers
+                         "true" "false")))
+     :hook fountain-export-tex-hook)
+    (fdx
+     :tag "Final Draft"
+     :ext ".fdx"
+     :template fountain-export-fdx-template
+     :string-replace (("&" "&#38;")
+                      ("<" "&#60;")
+                      (">" "&#62;")
+                      ("\"" "&#34;")
+                      ("'" "&#39;")
+                      ("\\\\_" "&#96;")
+                      ("\\\\\\*" "&#42;")
+                      ("_\\*\\*\\*\\(.+?\\)\\*\\*\\*_" "</Text><Text Style=\"Bold+Underline+Italic\">\\1</Text><Text>")
+                      ("\\*\\*\\*\\(.+?\\)\\*\\*\\*" "</Text><Text Style=\"Bold+Italic\">\\1</Text><Text>")
+                      ("_\\*\\*\\(.+?\\)\\*\\*_" "</Text><Text Style=\"Bold+Underline\">\\1</Text><Text>")
+                      ("_\\*\\(.+?\\)\\*_" "</Text><Text Style=\"Underline+Italic\">\\1</Text><Text>")
+                      ("\\*\\*\\(.+?\\)\\*\\*" "</Text><Text Style=\"Bold\">\\1</Text><Text>")
+                      ("\\*\\(.+?\\)\\*" "</Text><Text Style=\"Italic\">\\1</Text><Text>")
+                      ("^~\s*\\(.+?\\)$" "</Text><Text Style=\"Italic\">\\1</Text><Text>")
+                      ("_\\(.+?\\)_" "</Text><Text Style=\"Underline\">\\1</Text><Text>")
+                      ("\n\n+" "\n\n"))
+     :cond-replace ((t
+                     (starts-new-page
+                      (t "Yes") (nil "No"))))
+     :eval-replace ((title-page
+                     (when fountain-export-include-title-page
+                       fountain-export-fdx-title-page-template)))
+     :hook fountain-export-fdx-hook)
+    (fountain
+     :tag "Fountain"
+     :ext ".fountain"
+     :template fountain-export-fountain-template
+     :cond-replace ((scene-heading
+                     (forced (t ".")))
+                    (character
+                     (dual (right " ^"))
+                     (forced (t "@")))
+                    (trans
+                     (forced (t "> ")))
+                    (action
+                     (forced (t "!")))
+                    (section-heading
+                     (level (1 "#")
+                            (2 "##")
+                            (3 "###")
+                            (4 "####")
+                            (5 "#####"))))
+     :eval-replace ((title-page
+                     (when fountain-export-include-title-page
+                       fountain-export-fountain-title-page-template))
+                    (scene-heading-spacing
+                     (when (memq 'double-space fountain-export-scene-heading-format)
+                       "\n")))
+     :hook fountain-export-fountain-hook)
+    (txt
+     :tag "plaintext"
+     :ext ".txt"
+     :fill t
+     :template fountain-export-txt-template
+     :eval-replace ((scene-heading-spacing
+                     (when (memq 'double-space fountain-export-scene-heading-format)
+                       "\n"))
+                    (title-page
+                     (when fountain-export-include-title-page
+                       fountain-export-txt-title-page-template)))
+     :hook fountain-export-txt-hook))
+  "Association list of export formats and their properties.
+Takes the form:
+
+    ((FORMAT KEYWORD PROPERTY)
+      ...)")
+
 (defun define-fountain-export-template-docstring (format)
   (let ((tag (plist-get (cdr (assq format fountain-export-formats))
                         :tag)))
@@ -2576,12 +2532,11 @@
                  (choice string (const nil)))))
 
 (defun fountain-get-export-elements (&optional format)
-  "Returns list of elements exported in current format.
-Format defaults to \"screenplay\"."
+  "Returns list of elements exported in current script format."
   (cdr (or (assoc-string
             (or format
                 (plist-get (fountain-read-metadata) 'format)
-                "screenplay")
+                fountain-script-format)
             fountain-export-include-elements)
            (car fountain-export-include-elements))))
 
@@ -2595,13 +2550,11 @@
     (with-current-buffer (or buffer (current-buffer))
       (cond (fountain-export-use-title-as-filename
              (concat (plist-get (fountain-read-metadata) 'title) ext))
-            ((buffer-file-name)
-             (concat (file-name-base (buffer-file-name)) ext))
+            (buffer-file-name
+             (concat (file-name-base buffer-file-name) ext))
             (t
              (format fountain-export-tmp-buffer-name tag))))))
 
-(require 'subr-x)
-
 (defun fountain-slugify (string)
   "Convert STRING to one suitable for slugs.
 
@@ -2617,15 +2570,15 @@
       "[^[:alnum:]]+" t)
      "-")))
 
-(defun fountain-export-fill-string (string element)
+(defun fountain-export-fill-string (string element-type)
   (with-temp-buffer
     (insert string)
     (let (adaptive-fill-mode
-          (fill
-           (symbol-value
-            (plist-get (cdr (assq element fountain-elements)) :fill))))
-      (setq left-margin (car fill)
-            fill-column (+ left-margin (cdr fill)))
+          (fill-margins (symbol-value
+                 (plist-get (cdr (assq element-type fountain-elements))
+                            :fill))))
+      (setq left-margin (car fill-margins)
+            fill-column (+ left-margin (cdr fill-margins)))
       ;; Replace emphasis syntax with face text propoerties (before performing fill).
       (dolist (face '((fountain-italic-regexp . italic)
                       (fountain-bold-regexp . bold)
@@ -2643,9 +2596,9 @@
   (let ((replace-alist
          (plist-get (cdr (assq format fountain-export-formats))
                     :string-replace)))
-  (dolist (replacement replace-alist string)
-    (setq string (replace-regexp-in-string
-                  (car replacement) (cadr replacement) string t nil)))))
+    (dolist (replacement replace-alist string)
+      (setq string (replace-regexp-in-string
+                    (car replacement) (cadr replacement) string t nil)))))
 
 (defun fountain-export-get-cond-replacement (format element key value)
   (let ((replace-alist
@@ -2665,8 +2618,8 @@
                                     :eval-replace)))))
         string)
     (unwind-protect
-        (setq string (eval replacement))
-      (if (stringp string) string))))
+        (setq string (eval replacement t))
+      (when (stringp string) string))))
 
 (defun fountain-export-element (element-list format)
   "Return a formatted string from ELEMENT-LIST according to FORMAT.
@@ -2685,7 +2638,7 @@
 
 Check if ELEMENT corresponds to a template in
 `fountain-export-templates' and set ELEMENT-TEMPLATE. If so,
-replace matches of `fountain-template-key-regexp' in the
+replace matches of `fountain-template-regexp' in the
 following order:
 
 1. {{content}} is replaced with CONTENT.
@@ -2722,8 +2675,8 @@
            ((stringp content)
             (setq string (fountain-export-replace-in-string content format))
             ;; If the format is filled, fill STRING in temporary buffer
-            (if (plist-get export-format-plist :fill)
-                (setq string (fountain-export-fill-string string element))))
+            (when (plist-get export-format-plist :fill)
+              (setq string (fountain-export-fill-string string element))))
            ;; If CONTENT is a list, work through the list setting each element
            ;; as CHILD-ELEMENT-LIST and recursively calling this function.
            ((listp content)
@@ -2743,11 +2696,10 @@
            ;; If there is a FORMAT-TEMPLATE and an ELEMENT-TEMPLATE, replace
            ;; template keys in that template.
            ((and format-template element-template)
-            (while (string-match fountain-template-key-regexp element-template)
+            (while (string-match fountain-template-regexp element-template)
               (setq element-template
-                    ;; FIXME: can this be better written with pcase?
                     (replace-regexp-in-string
-                     fountain-template-key-regexp
+                     fountain-template-regexp
                      (lambda (match)
                        ;; Find KEY and corresponding VALUE in PLIST.
                        (let* ((key (match-string 1 match))
@@ -2766,6 +2718,11 @@
                           ;; If KEY's VALUE is not a string but still non-nil
                           ;; attempt conditional replacement based on KEY's
                           ;; VALUE.
+                          ;;
+                          ;; FIXME: the following two functions are ugly/messy.
+                          ;; Some work has already been done to combine these
+                          ;; into just `fountain-export-get-eval-replacement' by
+                          ;; using template {{KEYS}} in the export templates.
                           (value
                            (fountain-export-get-cond-replacement format element (intern key) value))
                           ;; Otherwise, attempt expression replacements.
@@ -2809,7 +2766,7 @@
                          (list 'begin start
                                'end end
                                'export t)
-                               (fountain-read-metadata))
+                         (fountain-read-metadata))
                         tree))))
     ;; Walk through TREE, concatenating exported elements to STRING.
     (while tree
@@ -2835,7 +2792,7 @@
           (completing-read "Export format: "
                            (mapcar #'car fountain-export-formats) nil t))
          (car current-prefix-arg)))
-  (setq buffer (or buffer (current-buffer)))
+  (unless buffer (setq buffer (current-buffer)))
   (let ((dest-buffer (get-buffer-create
                       (fountain-export-get-filename format buffer)))
         (hook (plist-get (cdr (assq format fountain-export-formats))
@@ -2846,14 +2803,14 @@
           ;; If DEST-BUFFER is not empty, check if it is the current buffer, or
           ;; if not, if the user does not wish to overwrite.
           (when (< 0 (buffer-size dest-buffer))
-            (if (or (eq (current-buffer) dest-buffer)
-                    (not (y-or-n-p (format "Buffer `%s' is not empty; overwrite? "
-                                           dest-buffer))))
-                ;; If so, generate a new buffer.
-                (progn
-                  (setq dest-buffer
-                        (generate-new-buffer (buffer-name dest-buffer)))
-                  (message "Using new buffer `%s'" dest-buffer))))
+            (when (or (eq (current-buffer) dest-buffer)
+                      (not (y-or-n-p
+                            (format "Buffer `%s' is not empty; overwrite? "
+                                    dest-buffer))))
+              ;; If so, generate a new buffer.
+              (setq dest-buffer
+                    (generate-new-buffer (buffer-name dest-buffer)))
+              (message "Using new buffer `%s'" dest-buffer)))
           ;; Export the region to STRING.
           (setq string
                 (fountain-export-region (point-min) (point-max) format snippet))
@@ -2873,18 +2830,21 @@
         (kill-buffer dest-buffer)))))
 
 (defun fountain-export-default ()
-  "Call function defined in `fountain-export-default-command'."
+  "Call function defined in `fountain-export-default-function'."
   (interactive)
-  (funcall fountain-export-default-command))
+  (funcall fountain-export-default-function))
 
 (defun fountain-export-shell-command (&optional buffer)
   "Call shell command defined in variable `fountain-export-shell-command'.
 Command acts on current buffer or BUFFER."
+  ;; FIXME: better to call ‘start-process’ directly, since it offers more
+  ;; control and does not impose the use of a shell (with its need to quote
+  ;; arguments).
   (interactive)
   (let* ((buffer (or buffer (current-buffer)))
          (file (buffer-file-name buffer)))
     (if file
-        (async-shell-command            ; FIXME use start-process
+        (async-shell-command
          (format fountain-export-shell-command (shell-quote-argument file))
          "*Fountain Export Process*")
       (user-error "Buffer `%s' is not visiting a file" buffer))))
@@ -2905,8 +2865,7 @@
 {{contact}}
 {{date}}\n\n"
   "Template for plaintext title page."
-  :type 'string
-  :group 'fountain-plaintext-export)
+  :type 'string)
 
 (defcustom fountain-export-txt-template
   '((document "{{title-page}}{{content}}")
@@ -2924,16 +2883,14 @@
     (page-break "\n\n")
     (synopsis "{{content}}\n\n")
     (note "[ note: {{content}} ]\n\n")
-    (center "{{content}}"))
+    (center "{{content}}\n\n"))
   (define-fountain-export-template-docstring 'txt)
-  :type 'fountain-element-list-type
-  :group 'fountain-plaintext-export)
+  :type 'fountain-element-list-type)
 
 (defcustom fountain-export-txt-hook
   nil
   "Hook run with export buffer on sucessful export to plaintext."
-  :type 'hook
-  :group 'fountain-plaintext-export)
+  :type 'hook)
 
 (defun fountain-export-buffer-to-txt ()
   "Convenience function for exporting buffer to plaintext."
@@ -2943,6 +2900,40 @@
 
 ;;; -> PostScript
 
+;; FIXME: Exporting to PostScript requires some further work before it can be
+;; implemented. Exporting to PostScript is essentially the existing export to
+;; plain text functions, with the following additions:
+;;
+;; When calling `fountain-prep-and-parse-region' for exporting to PostScript,
+;; the preparation phase should add a while-loop that begins at point-min and
+;; calls `fountain-forward-page' to move through the temporary buffer, inserting
+;; manual page-breaks by calling `fountain-insert-page-break' with each
+;; iteration. However the function `fountain-goto-page-break-point' needs to be
+;; improved to test for existing page-breaks immediately before or after point,
+;; to prevent inserting multiple consecutive page-breaks creating blank pages.
+;; This should be its own function, e.g. `fountain-paginate-buffer'.
+;;
+;; Once page-breaks have been inserted, `fountain-prep-and-parse-region' will
+;; return a lisp data tree of the buffer with appropriate page-breaks.
+;; Page-breaks need to then be inserted as linefeed (^L) characters in the
+;; destination buffer, to signal a page-break to the PostScript typesetter.
+;;
+;; Additionally, a user option for headers and footer formatting is required,
+;; which should include the page number at the right-hand-side of the header. It
+;; is important that if the header or footer encroaches into the space of the
+;; page, then `fountain-forward-page' reacts accordingly. (However it would be
+;; more reasonable to limit the header and footer each to a single line.)
+;;
+;; Finally, the variables `ps-paper-type', `ps-left-margin', `ps-top-margin',
+;; `ps-font-size', `ps-print-color-p' and `ps-print-header' need to be set
+;; accordingly before calling `ps-print-buffer'. (See below.)
+;;
+;; This may seem like an odd way to calculate page length, but if implemented
+;; well, should allow for a script to have "locked pages" by calling the
+;; aforementioned `fountain-paginate-buffer'. For more information on locking
+;; pages in a script, see:
+;; https://en.wikipedia.org/wiki/Shooting_script#Preserving_scene_and_page_numbers
+
 ;; (defgroup fountain-postscript-export ()
 ;;   "Options for exporting Fountain files to PostScript."
 ;;   :prefix 'fountain-export-ps-
@@ -3002,8 +2993,7 @@
 <p class=\"contact\">{{contact}}</p>
 </section>"
   "Template for HTML title page."
-  :type 'string
-  :group 'fountain-html-export)
+  :type 'string)
 
 (defcustom fountain-export-html-template
   '((document "\
@@ -3039,8 +3029,7 @@
     (note "<p class=\"note\">{{content}}</p>\n")
     (center "<p class=\"center\">{{content}}</p>\n"))
   (define-fountain-export-template-docstring 'html)
-  :type 'fountain-element-list-type
-  :group 'fountain-html-export)
+  :type 'fountain-element-list-type)
 
 (defcustom fountain-export-html-stylesheet
   "\
@@ -3163,8 +3152,7 @@
 means all screenplay elements require the \".screenplay\" class
 parent."
   :type 'string
-  :link '(url-link "https://github.com/rnkn/mcqueen")
-  :group 'fountain-html-export)
+  :link '(url-link "https://github.com/rnkn/mcqueen"))
 
 (defcustom fountain-export-html-title-template
   "<div class=\"title\">{{title-template}}</div>
@@ -3174,14 +3162,12 @@
 <p class=\"contact\">{{contact-template}}</p>
 "
   "HTML template for title page export."
-  :type 'string
-  :group 'fountain-html-export)
+  :type 'string)
 
 (defcustom fountain-export-html-hook
   nil
   "Hook run with export buffer on sucessful export to HTML."
-  :type 'hook
-  :group 'fountain-html-export)
+  :type 'hook)
 
 (defun fountain-export-buffer-to-html ()
   "Convenience function for exporting buffer to HTML."
@@ -3223,8 +3209,7 @@
 }
 \\clearpage"
   "Template for LaTeX title page."
-  :type 'string
-  :group 'fountain-latex-export)
+  :type 'string)
 
 (defcustom fountain-export-tex-template
   '((document "\
@@ -3401,14 +3386,12 @@
     (note nil)
     (center "\\centertext{{{content}}}\n\n"))
   (define-fountain-export-template-docstring 'tex)
-  :type 'fountain-element-list-type
-  :group 'fountain-latex-export)
+  :type 'fountain-element-list-type)
 
 (defcustom fountain-export-tex-hook
   nil
   "Hook run with export buffer on sucessful export to LaTeX."
-  :type 'hook
-  :group 'fountain-latex-export)
+  :type 'hook)
 
 (defun fountain-export-buffer-to-latex ()
   "Convenience function for exporting buffer to LaTeX."
@@ -3448,8 +3431,7 @@
 </Content>
 </TitlePage>"
   "Template for Final Draft title page."
-  :type 'string
-  :group 'fountain-final-draft-export)
+  :type 'string)
 
 (defcustom fountain-export-fdx-template
   '((document "\
@@ -3476,14 +3458,12 @@
     (note nil)
     (center "<Paragraph Alignment=\"Center\" Type=\"Action\" StartsNewPage=\"{{starts-new-page}}\">\n<Text>{{content}}</Text>\n</Paragraph>\n"))
   (define-fountain-export-template-docstring 'fdx)
-  :type 'fountain-element-list-type
-  :group 'fountain-final-draft-export)
+  :type 'fountain-element-list-type)
 
 (defcustom fountain-export-fdx-hook
   nil
   "Hook run with export buffer on sucessful export to Final Draft."
-  :type 'hook
-  :group 'fountain-final-draft-export)
+  :type 'hook)
 
 (defun fountain-export-buffer-to-fdx ()
   "Convenience function for exporting buffer to Final Draft."
@@ -3507,8 +3487,7 @@
 contact: {{contact}}"
   "Template for Fountain title page.
 This just adds the current metadata to the exported file."
-  :type 'string
-  :group 'fountain-fountain-export)
+  :type 'string)
 
 (defcustom fountain-export-fountain-template
   '((document "\
@@ -3531,14 +3510,12 @@
     (note "[[ {{content}} ]]\n\n")
     (center "> {{content}} <"))
   (define-fountain-export-template-docstring 'fountain)
-  :type 'fountain-element-list-type
-  :group 'fountain-fountain-export)
+  :type 'fountain-element-list-type)
 
 (defcustom fountain-export-fountain-hook
   nil
   "Hook run with export buffer on sucessful export to Fountain."
-  :type 'hook
-  :group 'fountain-fountain-export)
+  :type 'hook)
 
 (defun fountain-export-buffer-to-fountain ()
   "Convenience function for exporting buffer to Fountain."
@@ -3570,23 +3547,22 @@
 
 Used by `fountain-outline-cycle'.")
 
-(defcustom fountain-outline-startup-level
-  0
-  "Outline level to show when visiting a file."
-  :type '(choice (const :tag "Show all" 0)
-                 (const :tag "Show top-level" 1)
-                 (const :tag "Show scene headings" 6)
-                 (integer :tag "Custom level"))
-  :group 'fountain)
-
 (defcustom fountain-outline-custom-level
   nil
   "Additional section headings to include in outline cycling."
   :type '(choice (const :tag "Only top-level" nil)
-                 (const :tag "Include level 2" 2)
-                 (const :tag "Include level 3" 3)
-                 (const :tag "Include level 4" 4)
-                 (const :tag "Include level 5" 5))
+                 (const :tag "Level 2" 2)
+                 (const :tag "Level 3" 3)
+                 (const :tag "Level 4" 4)
+                 (const :tag "Level 5" 5))
+  :group 'fountain)
+
+(defcustom fountain-shift-all-elements
+  t
+  "\\<fountain-mode-map>Non-nil if \\[fountain-shift-up] and \\[fountain-shift-down] should operate on all elements.
+Otherwise, only operate on section and scene headings."
+  :type 'boolean
+  :safe 'boolean
   :group 'fountain)
 
 (defalias 'fountain-outline-next 'outline-next-visible-heading)
@@ -3609,6 +3585,126 @@
 If POS is nil, use `point' instead."
   (eq (get-char-property (or pos (point)) 'invisible) 'outline))
 
+(defun fountain-get-block-bounds ()
+  "Return the beginning and end bounds of current element block."
+  (let ((element (fountain-get-element))
+        begin end)
+    (when element
+      (save-excursion
+        (save-restriction
+          (widen)
+          (cond ((memq element '(section-heading scene-heading))
+                 (setq begin (match-beginning 0))
+                 (outline-end-of-subtree)
+                 (skip-chars-forward "\n\s\t")
+                 (setq end (point)))
+                ((memq element '(character paren lines))
+                 (fountain-forward-character 0)
+                 (setq begin (line-beginning-position))
+                 (while (not (or (eobp)
+                                 (and (bolp) (eolp))
+                                 (fountain-match-note)))
+                   (forward-line))
+                 (skip-chars-forward "\n\s\t")
+                 (setq end (point)))
+                ((memq element '(trans center synopsis note page-break))
+                 (setq begin (match-beginning 0))
+                 (goto-char (match-end 0))
+                 (skip-chars-forward "\n\s\t")
+                 (setq end (point)))
+                ((eq element 'action)
+                 (save-excursion
+                   (if (fountain-blank-before-p)
+                       (setq begin (line-beginning-position))
+                     (backward-char)
+                     (while (and (eq (fountain-get-element) 'action)
+                                 (not (bobp)))
+                       (forward-line -1))
+                     (skip-chars-forward "\n\s\t")
+                     (beginning-of-line)
+                     (setq begin (point))))
+                 (forward-line)
+                 (unless (eobp)
+                   (while (and (eq (fountain-get-element) 'action)
+                               (not (eobp)))
+                     (forward-line))
+                   (skip-chars-forward "\n\s\t")
+                   (beginning-of-line))
+                 (setq end (point))))))
+      (cons begin end))))
+
+(defun fountain-insert-hanging-line-maybe ()
+  "Insert a empty newline if needed.
+Return non-nil if empty newline was inserted."
+  (let (hanging-line)
+    (when (and (eobp) (/= (char-before) ?\n))
+      (insert "\n"))
+    (when (and (eobp) (not (fountain-blank-before-p)))
+      (insert "\n")
+      (setq hanging-line t))
+    (unless (eobp)
+      (forward-char 1))
+    hanging-line))
+
+(defun fountain-shift-down (&optional n)
+  "Move the current element down past an element of the same level."
+  (interactive "p")
+  (unless n (setq n 1))
+  (if (outline-on-heading-p)
+      (fountain-outline-shift-down n)
+    (when fountain-shift-all-elements
+      (let ((forward (< 0 n))
+            hanging-line)
+        (when (and (bolp) (eolp))
+          (funcall (if forward #'skip-chars-forward #'skip-chars-backward)
+                   "\n\s\t"))
+        (save-excursion
+          (save-restriction
+            (widen)
+            (let ((block-bounds (fountain-get-block-bounds))
+                  outline-begin outline-end next-block-bounds)
+              (unless (and (car block-bounds)
+                           (cdr block-bounds))
+                (user-error "Not at a moveable element"))
+              (save-excursion
+                (when (not forward)
+                  (goto-char (cdr block-bounds))
+                  (when (setq hanging-line (fountain-insert-hanging-line-maybe))
+                    (setcdr block-bounds (point)))
+                  (goto-char (car block-bounds)))
+                (outline-previous-heading)
+                (setq outline-begin (point))
+                (outline-next-heading)
+                (setq outline-end (point)))
+              (if forward
+                  (goto-char (cdr block-bounds))
+                (goto-char (car block-bounds))
+                (backward-char)
+                (skip-chars-backward "\n\s\t"))
+              (setq next-block-bounds (fountain-get-block-bounds))
+              (unless (and (car next-block-bounds)
+                           (cdr next-block-bounds))
+                (user-error "Cannot shift element any further"))
+              (when forward
+                (goto-char (cdr next-block-bounds))
+                (when (setq hanging-line (fountain-insert-hanging-line-maybe))
+                  (setcdr next-block-bounds (point))))
+              (unless (< outline-begin (car next-block-bounds) outline-end)
+                (user-error "Cannot shift past higher level"))
+              (goto-char (if forward (car block-bounds) (cdr block-bounds)))
+              (insert-before-markers
+               (delete-and-extract-region (car next-block-bounds)
+                                          (cdr next-block-bounds))))
+            (when hanging-line
+              (goto-char (point-max))
+              (delete-char -1))))))))
+
+(defun fountain-shift-up (&optional n)
+  "Move the current element up past an element of the same level."
+  (interactive "p")
+  (unless n (setq n 1))
+  (fountain-shift-down (- n)))
+
 (defun fountain-outline-shift-down (&optional n)
   "Move the current subtree down past N headings of same level."
   (interactive "p")
@@ -3621,18 +3717,7 @@
          (end-point-fun
           (lambda ()
             (outline-end-of-subtree)
-            ;; Add newline if none at eof.
-            (if (and (eobp)
-                     (/= (char-before) ?\n))
-                (insert-char ?\n))
-            ;; Temporary newline if only 1 at eof
-            (when (and (eobp)
-                       (not (fountain-blank-before-p)))
-              (insert-char ?\n)
-              (setq hanging-line t))
-            ;; Avoid eobp signal.
-            (unless (eobp)
-              (forward-char 1))
+            (setq hanging-line (fountain-insert-hanging-line-maybe))
             (point)))
          (beg (point))
          (folded
@@ -3650,18 +3735,15 @@
           (progn (goto-char beg)
                  (message "Cannot shift past higher level")))
       (setq i (1- i)))
-    (if (< 0 n)
-        (funcall end-point-fun))
+    (when (< 0 n) (funcall end-point-fun))
     (set-marker insert-point (point))
     (insert (delete-and-extract-region beg end))
     (goto-char insert-point)
-    (if folded
-        (outline-hide-subtree))
-    ;; Remove temporary newline.
-    (if hanging-line
-        (save-excursion
-          (goto-char (point-max))
-          (delete-char -1)))
+    (when folded (outline-hide-subtree))
+    (when hanging-line
+      (save-excursion
+        (goto-char (point-max))
+        (delete-char -1)))
     (set-marker insert-point nil)))
 
 (defun fountain-outline-shift-up (&optional n)
@@ -3683,31 +3765,32 @@
          (unless silent (message "Showing level %s headings" n))))
   (setq fountain--outline-cycle n))
 
+(defun fountain-outline-hide-custom-level ()
+  "Set the outline visibilty to `fountain-outline-custom-level'."
+  (when fountain-outline-custom-level
+    (fountain-outline-hide-level fountain-outline-custom-level t)))
+
 (defun fountain-outline-cycle (&optional arg) ; FIXME: document
   "\\<fountain-mode-map>Cycle outline visibility depending on ARG.
 
-1. If ARG is nil, cycle outline visibility of current subtree and
-   its children (\\[fountain-outline-cycle]).
-
-2. If ARG is 4, cycle outline visibility of buffer (\\[universal-argument] \\[fountain-outline-cycle],
-   same as \\[fountain-outline-cycle-global]).
-
-3. If ARG is 16, show all (\\[universal-argument] \\[universal-argument] \\[fountain-outline-cycle]).
-
-4. If ARG is 64, show outline visibility set in
-   `fountain-outline-custom-level' (\\[universal-argument] \\[universal-argument] \\[universal-argument] \\[fountain-outline-cycle])."
+    1. If ARG is nil, cycle outline visibility of current subtree and
+       its children (\\[fountain-outline-cycle]).
+    2. If ARG is 4, cycle outline visibility of buffer (\\[universal-argument] \\[fountain-outline-cycle],
+       same as \\[fountain-outline-cycle-global]).
+    3. If ARG is 16, show all (\\[universal-argument] \\[universal-argument] \\[fountain-outline-cycle]).
+    4. If ARG is 64, show outline visibility set in
+       `fountain-outline-custom-level' (\\[universal-argument] \\[universal-argument] \\[universal-argument] \\[fountain-outline-cycle])."
   (interactive "p")
   (let ((custom-level
-         (if fountain-outline-custom-level
-             (save-excursion
-               (goto-char (point-min))
-               (let (found)
-                 (while (and (not found)
-                             (outline-next-heading))
-                   (if (= (funcall outline-level)
-                          fountain-outline-custom-level)
-                       (setq found t)))
-                 (if found fountain-outline-custom-level)))))
+         (when fountain-outline-custom-level
+           (save-excursion
+             (goto-char (point-min))
+             (let (found)
+               (while (and (not found)
+                           (outline-next-heading))
+                 (when (= (funcall outline-level) fountain-outline-custom-level)
+                   (setq found t)))
+               (when found fountain-outline-custom-level)))))
         (highest-level
          (save-excursion
            (goto-char (point-max))
@@ -3750,10 +3833,10 @@
                       (point)))
                    (eol
                     (save-excursion
-                      (forward-line 1)
+                      (forward-line)
                       (while (and (not (eobp))
                                   (get-char-property (1- (point)) 'invisible))
-                        (forward-line 1))
+                        (forward-line))
                       (point)))
                    (children
                     (save-excursion
@@ -3788,13 +3871,10 @@
 Calls `fountain-outline-cycle' with argument 4 to cycle buffer
 outline visibility through the following states:
 
-1. Top-level section headings
-
-2. Value of `fountain-outline-custom-level'
-
-3. All section headings and scene headings
-
-4. Everything"
+    1. Top-level section headings
+    2. Value of `fountain-outline-custom-level'
+    3. All section headings and scene headings
+    4. Everything"
   (interactive)
   (fountain-outline-cycle 4))
 
@@ -3802,10 +3882,8 @@
   "Return the heading's nesting level in the outline.
 Assumes that point is at the beginning of a heading and match
 data reflects `outline-regexp'."
-  (cond ((string-match fountain-end-regexp (match-string 0))
-         1)
-        ((string-prefix-p "#" (match-string 0))
-         (string-width (match-string 2)))
+  (cond ((string-prefix-p "#" (match-string 0))
+         (string-width (match-string 1)))
         (t 6)))
 
 (defcustom fountain-pop-up-indirect-windows
@@ -3830,7 +3908,7 @@
         (setq beg (point))
         (when (or (fountain-match-section-heading)
                   (fountain-match-scene-heading))
-          (setq heading-name (match-string-no-properties 3)
+          (setq heading-name (match-string-no-properties 2)
                 target-buffer (concat base-buffer "-" heading-name))
           (outline-end-of-subtree)
           (setq end (point)))))
@@ -3839,7 +3917,7 @@
                (goto-char beg)
                (and (or (fountain-match-section-heading)
                         (fountain-match-scene-heading))
-                    (string= heading-name (match-string-no-properties 3)))))
+                    (string= heading-name (match-string-no-properties 2)))))
         (pop-to-buffer target-buffer)
       (clone-indirect-buffer target-buffer t)
       (outline-show-all))
@@ -3861,17 +3939,16 @@
               (forward-line p)))))
     (if (/= n 0)
         (while (/= n 0)
-          (if (fountain-match-scene-heading)
-              (forward-line p))
+          (when (fountain-match-scene-heading) (forward-line p))
           (funcall move-fun)
           (setq n (- n p)))
-      (forward-line 0)
+      (beginning-of-line)
       (funcall move-fun))))
 
 (defun fountain-backward-scene (&optional n)
   "Move backward N scene headings (foward if N is negative)."
   (interactive "^p")
-  (or n (setq n 1))
+  (unless n (setq n 1))
   (fountain-forward-scene (- n)))
 
 (defun fountain-beginning-of-scene ()   ; FIXME: needed?
@@ -3884,7 +3961,7 @@
   (interactive "^")
   (fountain-forward-scene 1)
   (unless (eobp)
-    (forward-char -1)))
+    (backward-char)))
 
 (defun fountain-mark-scene ()           ; FIXME: extending region
   "Put mark at end of this scene, point at beginning."
@@ -3919,14 +3996,14 @@
   (push-mark)
   (goto-char (point-min))
   (let ((scene (if (fountain-match-scene-heading)
-                   (car (fountain-scene-number-to-list (match-string 6)))
+                   (car (fountain-scene-number-to-list (match-string 8)))
                  0)))
     (while (and (< scene n)
                 (< (point) (point-max)))
       (fountain-forward-scene 1)
-      (if (fountain-match-scene-heading)
-          (setq scene (or (car (fountain-scene-number-to-list (match-string 6)))
-                          (1+ scene)))))))
+      (when (fountain-match-scene-heading)
+        (setq scene (or (car (fountain-scene-number-to-list (match-string 8)))
+                        (1+ scene)))))))
 
 (defun fountain-goto-page (n)
   "Move point to Nth appropropriate page in current buffer."
@@ -3959,106 +4036,23 @@
               (forward-line p)))))
     (if (/= n 0)
         (while (/= n 0)
-          (if (fountain-match-character)
-              (forward-line p))
+          (when (fountain-match-character) (forward-line p))
           (funcall move-fun)
           (setq n (- n p)))
-      (forward-line 0)
+      (beginning-of-line)
       (funcall move-fun))))
 
 (defun fountain-backward-character (&optional n)
   "Move backward N character (foward if N is negative)."
   (interactive "^p")
-  (setq n (or n 1))
+  (unless n (setq n 1))
   (fountain-forward-character (- n)))
 
 
-;;; Endnotes
-
-(defgroup fountain-endnotes ()
-  "Options for displaying endnotes.
-
-Fountain endnotes are kept at the end of a script following an
-endotes page break, defined as three or more \"=\" and the word
-\"end\" (case-insensitive).
-
-    === end [===]
-
-The endnotes section is a good place to keep extensive notes or
-scenes you want to move out of the script, but still wish to
-reference. Endnotes are not exported.
-
-WARNING: if using other Fountain apps, check to make sure they
-support endnotes."
-  :group 'fountain)
-
-(defcustom fountain-endnotes-buffer
-  "%s<endnotes>"
-  "Name of buffer in which to display file endnotes.
-`%s' is replaced with `buffer-name'.
-
-To hide this buffer from the buffer list, prefix with a space."
-  :type 'string
-  :group 'fountain-endnotes)
-
-(defcustom fountain-endnotes-display-alist
-  '((side . right)
-    (window-width . 40)
-    (slot . 1))
-  "Alist used to display endnotes buffer.
-
-See `display-buffer-in-side-window' for example options."
-  :type 'alist
-  :group 'fountain-endnotes)
-
-(defcustom fountain-endnotes-select-window
-  nil
-  "If non-nil, switch to endnotes window upon displaying it."
-  :type 'boolean
-  :group 'fountain-endnotes)
-
-(defun fountain-show-or-hide-endnotes ()
-  "Pop up a window containing endnotes of current buffer.
-
-Display a window containing an indirect clone of the current
-buffer, narrowed to the first endnotes page break to the end of
-buffer."
-  (interactive)
-  (set-buffer (or (buffer-base-buffer) (current-buffer)))
-  (save-excursion
-    (save-restriction
-      (widen)
-      (goto-char (point-min))
-      (let ((beg (if (re-search-forward fountain-end-regexp nil t)
-                     (point)))
-            (buffer (current-buffer))
-            (endnotes-buffer (format fountain-endnotes-buffer (buffer-name))))
-        (if beg
-            (if (get-buffer-window endnotes-buffer (selected-frame))
-                (delete-windows-on endnotes-buffer (selected-frame))
-              (display-buffer-in-side-window
-               (or (get-buffer endnotes-buffer)
-                   (make-indirect-buffer buffer endnotes-buffer t))
-               fountain-endnotes-display-alist)
-              (with-current-buffer endnotes-buffer
-                (narrow-to-region (1+ beg) (point-max)))
-              (if fountain-endnotes-select-window
-                  (select-window (get-buffer-window endnotes-buffer (selected-frame))))
-              (message "Showing `%s' endnotes; %s to hide" (buffer-name buffer)
-                       (key-description (where-is-internal this-command
-                                                           overriding-local-map t))))
-          (user-error "Buffer `%s' does not contain endnotes" (buffer-name)))))))
-
-
 ;;; Editing
 
 (require 'help)
 
-(defvar-local fountain--edit-line
-  nil
-  "Line number currently being edited.
-Prevents incomplete strings added to candidates.")
-
 (defun fountain-set-edit-line ()
   "Set `fountain--edit-line' to current line.
 
@@ -4080,8 +4074,10 @@
   nil
   "Overlay used for auto-upcasing current line.")
 
-(defcustom fountain-tab-command
-  'fountain-dwim
+(define-obsolete-variable-alias 'fountain-tab-command
+  'fountain-tab-function "fountain-mode-2.7.0")
+(defcustom fountain-tab-function
+  #'fountain-dwim
   "Command to call when pressing the TAB key."
   :type '(radio (function-item fountain-dwim)
                 (function-item fountain-outline-cycle)
@@ -4090,11 +4086,11 @@
   :group 'fountain)
 
 (defun fountain-tab-action (&optional arg)
-  "Simply calls the value of variable `fountain-tab-command'."
+  "Simply calls the value of variable `fountain-tab-function'."
   (interactive "p")
-  (if (help-function-arglist fountain-tab-command)
-      (funcall fountain-tab-command arg)
-    (funcall fountain-tab-command)))
+  (condition-case nil
+      (funcall fountain-tab-function arg)
+    (wrong-number-of-arguments (funcall fountain-tab-function))))
 
 (defun fountain-auto-upcase-make-overlay ()
   "Make the auto-upcase overlay on current line.
@@ -4104,8 +4100,8 @@
 
 Make the overlay and add the face
 `fountain-auto-upcase-highlight'."
-  (if (overlayp fountain--auto-upcase-overlay)
-      (delete-overlay fountain--auto-upcase-overlay))
+  (when (overlayp fountain--auto-upcase-overlay)
+    (delete-overlay fountain--auto-upcase-overlay))
   (setq fountain--auto-upcase-overlay
         (make-overlay (line-beginning-position 1)
                       (line-beginning-position 2) nil nil t))
@@ -4120,8 +4116,8 @@
             (and (integerp fountain--auto-upcase-line)
                  (/= fountain--auto-upcase-line (line-number-at-pos))))
     (setq fountain--auto-upcase-line nil)
-    (if (overlayp fountain--auto-upcase-overlay)
-        (delete-overlay fountain--auto-upcase-overlay))
+    (when (overlayp fountain--auto-upcase-overlay)
+      (delete-overlay fountain--auto-upcase-overlay))
     (message "Auto-upcasing deactivated")))
 
 (defun fountain-toggle-auto-upcase ()
@@ -4157,7 +4153,7 @@
            (setq fountain--auto-upcase-line (line-number-at-pos))
            (message "Auto-upcasing activated"))
          (fountain-auto-upcase-make-overlay)
-         (upcase-region (line-beginning-position) (or (match-end 3) (point))))
+         (upcase-region (line-beginning-position) (or (match-end 2) (point))))
         ((and (integerp fountain--auto-upcase-line)
               (= fountain--auto-upcase-line (line-number-at-pos)))
          (fountain-auto-upcase-make-overlay)
@@ -4166,21 +4162,24 @@
 (defun fountain-dwim (&optional arg)
   "\\<fountain-mode-map>Call a command based on context (Do What I Mean).
 
-1. If point is at a scene heading or section heading, or if
-   prefixed with ARG call `fountain-outline-cycle' and pass ARG.
-
-2. If point is at an directive to an included file, call
-   `fountain-include-find-file'.
+1. If point is at a scene heading or section heading, or within a
+   folded scene or section, or if prefixed with ARG, call
+   `fountain-outline-cycle' and pass ARG.
+
+2. If point is at a directive to an included file, call
+   `fountain-find-included-file'.
 
 3. Otherwise, call `fountain-toggle-auto-upcase'."
   (interactive "p")
   (cond ((and arg (< 1 arg))
          (fountain-outline-cycle arg))
         ((or (fountain-match-section-heading)
-             (fountain-match-scene-heading))
+             (fountain-match-scene-heading)
+             (eq (get-char-property (point) 'invisible) 'outline))
          (fountain-outline-cycle))
-        ((fountain-match-include)
-         (fountain-include-find-file))
+        ((and (fountain-match-template)
+              (string-match-p "include" (match-string 1)))
+         (fountain-find-included-file))
         (t
          (fountain-toggle-auto-upcase))))
 
@@ -4189,7 +4188,7 @@
 If prefixed with ARG, insert `.' at beginning of line to force
 a scene heading."
   (interactive "P")
-  (if arg (save-excursion (forward-line 0) (insert ".")))
+  (when arg (save-excursion (beginning-of-line) (insert ".")))
   (upcase-region (line-beginning-position) (line-end-position)))
 
 (defun fountain-upcase-line-and-newline (&optional arg)
@@ -4197,13 +4196,12 @@
 If prefixed with ARG, insert `.' at beginning of line to force
 a scene heading."
   (interactive "P")
-  (if arg
-      (unless (fountain-match-scene-heading)
-        (save-excursion
-          (forward-line 0)
-          (insert "."))))
+  (when (and arg (not (fountain-match-scene-heading)))
+    (save-excursion
+      (beginning-of-line)
+      (insert ".")))
   (upcase-region (line-beginning-position) (point))
-  (newline))
+  (insert "\n"))
 
 (defun fountain-delete-comments-in-region (start end)
   "Delete comments in region between START and END."
@@ -4222,14 +4220,14 @@
   (interactive)
   (widen)
   (when (outline-back-to-heading)
-    (forward-line 1)
+    (forward-line)
     (or (bolp) (newline))
     (unless (and (bolp) (eolp)
                  (fountain-blank-after-p))
       (save-excursion
         (newline)))
     (insert "= ")
-    (if (outline-invisible-p) (fountain-outline-cycle))))
+    (when (outline-invisible-p) (fountain-outline-cycle))))
 
 (defun fountain-insert-note (&optional arg)
   "Insert a note based on `fountain-note-template' underneath current element.
@@ -4249,10 +4247,13 @@
       (comment-indent)
       (insert
        (replace-regexp-in-string
-        fountain-template-key-regexp
+        fountain-template-regexp
         (lambda (match)
           (let ((key (match-string 1 match)))
             (cdr
+             ;; FIXME: rather than hard-code limited options, these could work
+             ;; better if reusing the key-value replacement code from
+             ;; `fountain-export-element'.
              (assoc-string key (list (cons 'title (file-name-base (buffer-name)))
                                      (cons 'time (format-time-string fountain-time-format))
                                      (cons 'fullname user-full-name)
@@ -4286,11 +4287,12 @@
                      (delete-region (match-beginning 0) (match-end 0))))
                  (progress-reporter-update job))))
             case-fold-search)
-        (if (string= fountain-continued-dialog-string backup)
-            (setq backup (eval (car (get 'fountain-continued-dialog-string
-                                         'standard-value)))))
+        (when (string= fountain-continued-dialog-string backup)
+          (setq backup (eval (car (get 'fountain-continued-dialog-string
+                                       'standard-value))
+                             t)))
         ;; Delete all matches of backup string.
-        (if (stringp backup) (funcall replace-fun backup job))
+        (when (stringp backup) (funcall replace-fun backup job))
         ;; Delete all matches of current string.
         (funcall replace-fun fountain-continued-dialog-string job)
         ;; When fountain-add-continued-dialog, add string where appropriate.
@@ -4304,7 +4306,7 @@
                                 (fountain-get-character -1 'scene)))
               (re-search-forward "\s*$" (line-end-position) t)
               (replace-match (concat "\s" fountain-continued-dialog-string)))
-            (forward-line 1)
+            (forward-line)
             (progress-reporter-update job)))
         (progress-reporter-done job)))))
 
@@ -4332,12 +4334,14 @@
 WARNING: Using conflicting revised scene number format in the
 same script may result in errors in output."
   :type 'boolean
+  :safe 'booleanp
   :group 'fountain)
 
 (defcustom fountain-scene-number-first-revision
   ?A
   "Character to start revised scene numbers."
   :type 'character
+  :safe 'characterp
   :group 'fountain-scene-number)
 
 (defcustom fountain-scene-number-separator
@@ -4345,9 +4349,12 @@
   "character to separate scene numbers."
   :type '(choice (const nil)
                  (character ?-))
+  :safe '(lambda (value)
+           (or (null value)
+               (characterp value)))
   :group 'fountain-scene-number)
 
-(defun fountain-scene-number-to-list (string) ; FIXME: alternate separators and starting char
+(defun fountain-scene-number-to-list (string)
   "Read scene number STRING and return a list.
 
 If `fountain-prefix-revised-scene-numbers' is non-nil:
@@ -4359,6 +4366,8 @@
 
     \"10\" -> (10)
     \"10AA\" -> (10 1 1)"
+  ;; FIXME: does not account for user option `fountain-scene-number-separator'
+  ;; or `fountain-scene-number-first-revision'.
   (let (number revision)
     (when (stringp string)
       (if fountain-prefix-revised-scene-numbers
@@ -4386,16 +4395,16 @@
     (9 1 2) -> \"9AB\""
   (let ((number (car scene-num-list))
         separator revision)
-    (if (< 1 (length scene-num-list))
-        (setq separator
-              (if fountain-scene-number-separator
-                  (char-to-string fountain-scene-number-separator)
-                "")
-              revision
-              (mapconcat #'(lambda (char)
-                             (char-to-string
-                              (+ (1- char) fountain-scene-number-first-revision)))
-                         (cdr scene-num-list) separator)))
+    (when (< 1 (length scene-num-list))
+      (setq separator
+            (if fountain-scene-number-separator
+                (char-to-string fountain-scene-number-separator)
+              "")
+            revision
+            (mapconcat #'(lambda (char)
+                           (char-to-string
+                            (+ (1- char) fountain-scene-number-first-revision)))
+                       (cdr scene-num-list) separator)))
     (if fountain-prefix-revised-scene-numbers
         (progn
           (unless (string-empty-p revision) (setq number (1+ number)))
@@ -4428,13 +4437,13 @@
         (save-match-data
           (goto-char (point-min))
           (while (not (or found (eobp)))
-            (if (and (re-search-forward fountain-scene-heading-regexp nil 'move)
-                     (match-string 6))
-                (setq found t))))
+            (when (and (re-search-forward fountain-scene-heading-regexp nil 'move)
+                       (match-string 8))
+              (setq found t))))
         (if found
             ;; There are scene numbers, so this scene number needs to be
             ;; calculated relative to those.
-            (let ((current-scene (fountain-scene-number-to-list (match-string 6)))
+            (let ((current-scene (fountain-scene-number-to-list (match-string 8)))
                   last-scene next-scene)
               ;; Check if scene heading is already numbered and if there is a
               ;; NEXT-SCENE. No previousscene number can be greater or equal to
@@ -4442,8 +4451,8 @@
               (goto-char x)
               (while (not (or next-scene (eobp)))
                 (fountain-forward-scene 1)
-                (if (fountain-match-scene-heading)
-                    (setq next-scene (fountain-scene-number-to-list (match-string 6)))))
+                (when (fountain-match-scene-heading)
+                  (setq next-scene (fountain-scene-number-to-list (match-string 8)))))
               (cond
                ;; If there's both a NEXT-SCENE and CURRENT-SCENE, but NEXT-SCENE
                ;; is less or equal to CURRENT-SCENE, scene numbers are out of
@@ -4462,10 +4471,10 @@
                 (goto-char (point-min))
                 (unless (fountain-match-scene-heading)
                   (fountain-forward-scene 1))
-                (if (<= (point) x)
-                    (setq current-scene
-                          (or (fountain-scene-number-to-list (match-string 6))
-                              (list 1))))
+                (when (<= (point) x)
+                  (setq current-scene
+                        (or (fountain-scene-number-to-list (match-string 8))
+                            (list 1))))
                 ;; While before point X, go forward through each scene heading,
                 ;; setting LAST-SCENE to CURRENT-SCENE and CURRENT-SCENE to an
                 ;; incement of (car LAST-SCENE).
@@ -4473,7 +4482,7 @@
                   (fountain-forward-scene 1)
                   (when (fountain-match-scene-heading)
                     (setq last-scene current-scene
-                          current-scene (or (fountain-scene-number-to-list (match-string 6))
+                          current-scene (or (fountain-scene-number-to-list (match-string 8))
                                             (list (1+ (car last-scene)))))
                     ;; However, this might make CURRENT-SCENE greater or equal
                     ;; to NEXT-SCENE (a problem), so if there is a NEXT-SCENE,
@@ -4497,8 +4506,9 @@
                         (setq n (pop last-scene)
                               current-scene (append tmp-scene (list (1+ (or n 0))))
                               tmp-scene (append tmp-scene (list (or n 1))))
-                        (if (version-list-<= next-scene tmp-scene)
-                            (user-error err-order (fountain-scene-number-to-string current-scene)))))))
+                        (when (version-list-<= next-scene tmp-scene)
+                          (user-error err-order
+                                      (fountain-scene-number-to-string current-scene)))))))
                 current-scene)))
           ;; Otherwise there were no scene numbers, so we can just count
           ;; the scenes.
@@ -4508,8 +4518,8 @@
           (let ((current-scene 1))
             (while (< (point) x)
               (fountain-forward-scene 1)
-              (if (fountain-match-scene-heading)
-                  (setq current-scene (1+ current-scene))))
+              (when (fountain-match-scene-heading)
+                (setq current-scene (1+ current-scene))))
             (list current-scene)))))))
 
 (defun fountain-remove-scene-numbers ()
@@ -4524,9 +4534,8 @@
           (fountain-forward-scene 1))
         (while (and (fountain-match-scene-heading)
                     (< (point) (point-max)))
-          (if (match-string 6)
-              (delete-region (match-beginning 4)
-                             (match-end 7)))
+          (when (match-string 8)
+            (delete-region (match-beginning 6) (match-end 9)))
           (fountain-forward-scene 1))))))
 
 (defun fountain-add-scene-numbers ()
@@ -4566,7 +4575,7 @@
           (fountain-forward-scene 1))
         (while (and (fountain-match-scene-heading)
                     (< (point) (point-max)))
-          (unless (match-string 6)
+          (unless (match-string 8)
             (end-of-line)
             (delete-horizontal-space t)
             (insert "\s#" (fountain-scene-number-to-string (fountain-get-scene-number)) "#"))
@@ -4580,9 +4589,8 @@
 (defvar fountain-font-lock-keywords-plist
   `(;; Action
     ((lambda (limit)
-       (fountain-match-element 'fountain-match-action limit))
-     ((:level 1 :subexp 0 :face fountain-action
-              :invisible action)
+       (fountain-match-element #'fountain-match-action limit))
+     ((:level 1 :subexp 0 :face fountain-action)
       (:level 2 :subexp 1 :face fountain-non-printing
               :invisible fountain-syntax-chars
               :override t
@@ -4590,39 +4598,36 @@
      fountain-align-action)
     ;; Section Headings
     (,fountain-section-heading-regexp
-     ((:level 2 :subexp 0 :face fountain-section-heading
-              :invisible section-heading)
-      (:level 2 :subexp 2 :face fountain-non-printing
+     ((:level 2 :subexp 0 :face fountain-section-heading)
+      (:level 2 :subexp 1 :face fountain-non-printing
               :override t))
      fountain-align-scene-heading)
     ;; Scene Headings
     ((lambda (limit)
-       (fountain-match-element 'fountain-match-scene-heading limit))
-     ((:level 2 :subexp 0 :face fountain-scene-heading
-              :invisible scene-heading)
-      (:level 2 :subexp 2 :face fountain-non-printing
-              :invisible fountain-syntax-chars
-              :override prepend
-              :laxmatch t)
-      (:level 2 :subexp 4
-              :laxmatch t)
-      (:level 2 :subexp 5 :face fountain-non-printing
+       (fountain-match-element #'fountain-match-scene-heading limit))
+     ((:level 2 :subexp 0 :face fountain-scene-heading)
+      (:level 2 :subexp 1 :face fountain-non-printing
               :invisible fountain-syntax-chars
               :override prepend
               :laxmatch t)
       (:level 2 :subexp 6
+              :laxmatch t)
+      (:level 2 :subexp 7 :face fountain-non-printing
+              :invisible fountain-syntax-chars
               :override prepend
               :laxmatch t)
-      (:level 2 :subexp 7 :face fountain-non-printing
+      (:level 2 :subexp 8
+              :override prepend
+              :laxmatch t)
+      (:level 2 :subexp 9 :face fountain-non-printing
               :invisible fountain-syntax-chars
               :override prepend
               :laxmatch t))
      fountain-align-scene-heading)
     ;; Character
     ((lambda (limit)
-       (fountain-match-element 'fountain-match-character limit))
-     ((:level 3 :subexp 0 :face fountain-character
-              :invisible character)
+       (fountain-match-element #'fountain-match-character limit))
+     ((:level 3 :subexp 0 :face fountain-character)
       (:level 3 :subexp 2
               :invisible fountain-syntax-chars
               :override t
@@ -4633,22 +4638,19 @@
      fountain-align-character)
     ;; Parenthetical
     ((lambda (limit)
-       (fountain-match-element 'fountain-match-paren limit))
-     ((:level 3 :subexp 0 :face fountain-paren
-              :invisible paren))
+       (fountain-match-element #'fountain-match-paren limit))
+     ((:level 3 :subexp 0 :face fountain-paren))
      fountain-align-paren)
     ;; Dialog
     ((lambda (limit)
-       (fountain-match-element 'fountain-match-dialog limit))
-     ((:level 3 :subexp 0 :face fountain-dialog
-              :invisible dialog))
+       (fountain-match-element #'fountain-match-dialog limit))
+     ((:level 3 :subexp 0 :face fountain-dialog))
      fountain-align-dialog)
     ;; Transition
     ((lambda (limit)
-       (fountain-match-element 'fountain-match-trans limit))
-     ((:level 3 :subexp 0 :face fountain-trans
-              :invisible trans)
-      (:level 2 :subexp 2 :face fountain-comment
+       (fountain-match-element #'fountain-match-trans limit))
+     ((:level 3 :subexp 0 :face fountain-trans)
+      (:level 2 :subexp 1 :face fountain-comment
               :invisible fountain-syntax-chars
               :override t
               :laxmatch t))
@@ -4658,40 +4660,34 @@
      ((:level 2 :subexp 2 :face fountain-comment
               :invisible fountain-syntax-chars
               :override t)
-      (:level 3 :subexp 3
-              :invisible center)
+      (:level 3 :subexp 3)
       (:level 2 :subexp 4 :face fountain-comment
               :invisible fountain-syntax-chars
               :override t))
      fountain-align-center)
     ;; Page-break
     (,fountain-page-break-regexp
-     ((:level 2 :subexp 0 :face fountain-page-break
-              :invisible page-break)
+     ((:level 2 :subexp 0 :face fountain-page-break)
       (:level 2 :subexp 2 :face fountain-page-number
               :override t
               :laxmatch t)))
     ;; Synopses
     (,fountain-synopsis-regexp
-     ((:level 2 :subexp 0 :face fountain-synopsis
-              :invisible synopsis)
+     ((:level 2 :subexp 0 :face fountain-synopsis)
       (:level 2 :subexp 2 :face fountain-comment
               :invisible fountain-syntax-chars
               :override t))
      fountain-align-synopsis)
     ;; Notes
     (,fountain-note-regexp
-     ((:level 2 :subexp 0 :face fountain-note
-              :invisible note)))
-    ;; Inclusions
-    (,fountain-include-regexp
-     ((:level 2 :subexp 0 :face fountain-include
-              :invisible include)))
+     ((:level 2 :subexp 0 :face fountain-note)))
+    ;; Templates
+    (,fountain-template-regexp
+     ((:level 2 :subexp 0 :face fountain-template)))
     ;; Metedata
     ((lambda (limit)
-       (fountain-match-element 'fountain-match-metadata limit))
+       (fountain-match-element #'fountain-match-metadata limit))
      ((:level 2 :subexp 0 :face fountain-metadata-key
-              :invisible metadata
               :laxmatch t)
       (:level 2 :subexp 3 :face fountain-metadata-value
               :override t
@@ -4795,19 +4791,13 @@
                  (cond ((= n 1) "minimum")
                        ((= n 2) "default")
                        ((= n 3) "maximum")))
-        (font-lock-refresh-defaults)
-        (font-lock-ensure (save-excursion
-                            (goto-char (point-min))
-                            (re-search-forward fountain-end-regexp nil 'move)
-                            (point))
-                          (point-max)))
+        (font-lock-refresh-defaults))
     (user-error "Decoration must be an integer 1-3")))
 
 (defun fountain-create-font-lock-keywords ()
   "Return a new list of `font-lock-mode' keywords.
 Uses `fountain-font-lock-keywords-plist' to create a list of
 keywords suitable for Font Lock."
-  (fountain-init-vars)
   (let ((dec (fountain-get-font-lock-decoration))
         keywords)
     (dolist (var fountain-font-lock-keywords-plist keywords)
@@ -4815,20 +4805,19 @@
             (plist-list (nth 1 var))
             (align (fountain-get-align (symbol-value (nth 2 var))))
             align-props facespec)
-        (if (and align fountain-align-elements)
-            (setq align-props
-                  `(line-prefix
-                    (space :align-to ,align)
-                    wrap-prefix
-                    (space :align-to ,align))))
+        (when (and align fountain-align-elements)
+          (setq align-props
+                `(line-prefix
+                  (space :align-to ,align)
+                  wrap-prefix
+                  (space :align-to ,align))))
         (dolist (var plist-list)
           (let ((subexp (plist-get var :subexp))
-                (face (if (<= (plist-get var :level) dec)
-                          (plist-get var :face)))
+                (face (when (<= (plist-get var :level) dec)
+                        (plist-get var :face)))
                 (invisible (plist-get var :invisible))
                 invisible-props)
-            (if invisible
-                (setq invisible-props (list 'invisible invisible)))
+            (when invisible (setq invisible-props (list 'invisible invisible)))
             (setq facespec
                   (append facespec
                           (list `(,subexp '(face ,face
@@ -4845,23 +4834,23 @@
   (let (match)
     (while (and (null match)
                 (< (point) limit))
-      (if (funcall fun)
-          (setq match t))
-      (forward-line 1))
+      (when (funcall fun) (setq match t))
+      (forward-line))
     match))
 
 (defun fountain-redisplay-scene-numbers (start end)
+  ;; FIXME: Why use jit-lock rather than font-lock?
   (goto-char start)
   (while (< (point) (min end (point-max)))
-    (if (fountain-match-scene-heading)
-        (if (and fountain-display-scene-numbers-in-margin
-                 (match-string 6))
-            (put-text-property (match-beginning 4) (match-end 7)
-                               'display (list '(margin right-margin)
-                                              (match-string-no-properties 6)))
-          (remove-text-properties (match-beginning 0) (match-end 0)
-                                  '(display))))
-    (forward-line 1)))
+    (when (fountain-match-scene-heading)
+      (if (and fountain-display-scene-numbers-in-margin
+               (match-string 8))
+          (put-text-property (match-beginning 6) (match-end 9)
+                             'display (list '(margin right-margin)
+                                            (match-string-no-properties 8)))
+        (remove-text-properties (match-beginning 0) (match-end 0)
+                                '(display))))
+    (forward-line)))
 
 
 ;;; Key Bindings
@@ -4883,35 +4872,34 @@
     (define-key map (kbd "C-c C-x RET") #'fountain-insert-page-break)
     (define-key map (kbd "M-TAB") #'completion-at-point)
     (define-key map (kbd "C-c C-x a") #'fountain-completion-update)
-    ;; FIXME: include-find-file feels like it should be C-c C-c...
+    ;; FIXME: `fountain-include-find-file' feels like it should be C-c C-c,
+    ;; (currently mapped to `fountain-upcase-line').
     ;; (define-key map (kbd "C-c C-c") #'fountain-include-find-file)
     ;; Navigation commands:
-    (define-key map [remap forward-list] #'fountain-forward-scene)
-    (define-key map [remap backward-list] #'fountain-backward-scene)
     (define-key map [remap beginning-of-defun] #'fountain-beginning-of-scene)
     (define-key map [remap end-of-defun] #'fountain-end-of-scene)
-    (define-key map [remap mark-defun] #'fountain-mark-scene)
     (define-key map (kbd "M-g s") #'fountain-goto-scene)
     (define-key map (kbd "M-g p") #'fountain-goto-page)
     (define-key map (kbd "M-n") #'fountain-forward-character)
     (define-key map (kbd "M-p") #'fountain-backward-character)
+    ;; Block editing commands:
+    (define-key map (kbd "<M-down>") #'fountain-shift-down)
+    (define-key map (kbd "ESC <down>") #'fountain-shift-down)
+    (define-key map (kbd "<M-up>") #'fountain-shift-up)
+    (define-key map (kbd "ESC <up>") #'fountain-shift-up)
     ;; Outline commands:
-    (define-key map (kbd "C-c C-n") #'fountain-outline-next)
-    (define-key map (kbd "C-c C-p") #'fountain-outline-previous)
-    (define-key map (kbd "C-c C-f") #'fountain-outline-forward)
-    (define-key map (kbd "C-c C-b") #'fountain-outline-backward)
-    (define-key map (kbd "C-c C-u") #'fountain-outline-up)
-    (define-key map (kbd "C-c C-^") #'fountain-outline-shift-up)
-    (define-key map (kbd "C-c C-v") #'fountain-outline-shift-down)
-    (define-key map (kbd "C-c C-SPC") #'fountain-outline-mark)
+    (define-key map [remap forward-list] #'fountain-outline-next)
+    (define-key map [remap backward-list] #'fountain-outline-previous)
+    (define-key map [remap forward-sexp] #'fountain-outline-forward)
+    (define-key map [remap backward-sexp] #'fountain-outline-backward)
+    (define-key map [remap backward-up-list] #'fountain-outline-up)
+    (define-key map [remap mark-defun] #'fountain-outline-mark)
     (define-key map (kbd "C-c TAB") #'fountain-outline-cycle)
     (define-key map (kbd "<backtab>") #'fountain-outline-cycle-global)
     (define-key map (kbd "S-TAB") #'fountain-outline-cycle-global)
     (define-key map (kbd "C-c C-x b") #'fountain-outline-to-indirect-buffer)
     ;; Pages
     (define-key map (kbd "C-c C-x p") #'fountain-count-pages)
-    ;; Endnotes:
-    (define-key map (kbd "M-s e") #'fountain-show-or-hide-endnotes)
     ;; Exporting commands:
     (define-key map (kbd "C-c C-e e") #'fountain-export-buffer)
     (define-key map (kbd "C-c C-e C-e") #'fountain-export-default)
@@ -4933,38 +4921,43 @@
   "Menu for `fountain-mode'."
   '("Fountain"
     ("Navigate"
-     ["Next Scene Heading" fountain-forward-scene]
-     ["Previous Scene Heading" fountain-backward-scene]
+     ["Next Heading" fountain-outline-next]
+     ["Previous Heading" fountain-outline-previous]
+     ["Up Heading" fountain-outline-up]
+     ["Forward Heading Same Level" fountain-outline-forward]
+     ["Backward Heading Same Level" fountain-outline-backward]
+     "---"
+     ["Cycle Outline Visibility" fountain-outline-cycle]
+     ["Cycle Global Outline Visibility" fountain-outline-cycle-global]
+     ;; FIXME: this would be better as an alias, i.e.
+     ;; `fountain-outline-show-all'
+     ["Show All" outline-show-all]
      "---"
      ["Next Character" fountain-forward-character]
      ["Previous Character" fountain-backward-character]
      "---"
      ["Go to Scene Heading..." fountain-goto-scene]
      ["Go to Page..." fountain-goto-page])
-    ("Outline"
-     ["Cycle Scene/Section Visibility" fountain-outline-cycle]
-     ["Cycle Global Visibility" fountain-outline-cycle-global]
-     "---"
-     ["Open Scene/Section in Indirect Buffer" fountain-outline-to-indirect-buffer]
+    ("Edit Structure"
+     ["Mark Subtree" fountain-outline-mark]
+     ["Open Subtree in Indirect Buffer" fountain-outline-to-indirect-buffer]
      "---"
-     ["Up Heading" fountain-outline-up]
-     ["Next Heading" fountain-outline-next]
-     ["Previous Heading" fountain-outline-previous]
-     ["Forward Heading" fountain-outline-forward]
-     ["Backward Heading" fountain-outline-backward]
+     ["Shift Element Up" fountain-shift-up]
+     ["Shift Element Down" fountain-shift-down]
      "---"
-     ["Mark Section/Scene" fountain-outline-mark]
-     ["Shift Section/Scene Up" fountain-outline-shift-up]
-     ["Shift Section/Scene Down" fountain-outline-shift-down])
+     ["Shift All Elements" (customize-set-variable 'fountain-shift-all-elements
+                                                   (not fountain-shift-all-elements))
+      :style toggle
+      :selected fountain-shift-all-elements])
     ("Scene Numbers"
      ["Add Scene Numbers" fountain-add-scene-numbers]
      ["Remove Scene Numbers" fountain-remove-scene-numbers]
      "---"
      ["Display Scene Numbers in Margin"
-     (customize-set-variable 'fountain-display-scene-numbers-in-margin
-                             (not fountain-display-scene-numbers-in-margin))
-     :style toggle
-     :selected fountain-display-scene-numbers-in-margin])
+      (customize-set-variable 'fountain-display-scene-numbers-in-margin
+                              (not fountain-display-scene-numbers-in-margin))
+      :style toggle
+      :selected fountain-display-scene-numbers-in-margin])
     ("Page Numbers"
      ["Count Pages" fountain-count-pages]
      "---"
@@ -4988,32 +4981,6 @@
     ["Refresh Continued Dialog" fountain-continued-dialog-refresh]
     ["Update Auto-Completion" fountain-completion-update]
     "---"
-    ("Show/Hide"
-     ["Endnotes" fountain-show-or-hide-endnotes]
-     ["Hide Emphasis Delimiters"
-      (customize-set-variable 'fountain-hide-emphasis-delim
-                              (not fountain-hide-emphasis-delim))
-      :style toggle
-      :selected fountain-hide-emphasis-delim]
-     ["Hide Syntax Characters"
-      (customize-set-variable 'fountain-hide-syntax-chars
-                              (not fountain-hide-syntax-chars))
-      :style toggle
-      :selected fountain-hide-syntax-chars])
-    ("Syntax Highlighting"
-     ["Minimum"
-      (fountain-set-font-lock-decoration 1)
-      :style radio
-      :selected (= (fountain-get-font-lock-decoration) 1)]
-     ["Default"
-      (fountain-set-font-lock-decoration 2)
-      :style radio
-      :selected (= (fountain-get-font-lock-decoration) 2)]
-     ["Maximum"
-      (fountain-set-font-lock-decoration 3)
-      :style radio
-      :selected (= (fountain-get-font-lock-decoration) 3)])
-    "---"
     ("Export"
      ["Export buffer..." fountain-export-buffer]
      ["Default" fountain-export-default]
@@ -5070,7 +5037,7 @@
      ["Contextual (Do What I Mean)" (customize-set-variable 'fountain-tab-command 'fountain-dwim)
       :style radio
       :selected (eq fountain-tab-command 'fountain-dwim)]
-     ["Cycle Scene/Section Visibility" (customize-set-variable 'fountain-tab-command 'fountain-outline-cycle)
+     ["Cycle Outline Visibility" (customize-set-variable 'fountain-tab-command 'fountain-outline-cycle)
       :style radio
       :selected (eq fountain-tab-command 'fountain-outline-cycle)]
      ["Toggle Auto-Upcasing" (customize-set-variable 'fountain-tab-command 'fountain-toggle-auto-upcase)
@@ -5079,6 +5046,30 @@
      ["Auto-Completion" (customize-set-variable 'fountain-tab-command 'completion-at-point)
       :style radio
       :selected (eq fountain-tab-command 'completion-at-point)])
+    ("Syntax Highlighting"
+     ["Minimum"
+      (fountain-set-font-lock-decoration 1)
+      :style radio
+      :selected (= (fountain-get-font-lock-decoration) 1)]
+     ["Default"
+      (fountain-set-font-lock-decoration 2)
+      :style radio
+      :selected (= (fountain-get-font-lock-decoration) 2)]
+     ["Maximum"
+      (fountain-set-font-lock-decoration 3)
+      :style radio
+      :selected (= (fountain-get-font-lock-decoration) 3)]
+     "---"
+     ["Hide Emphasis Delimiters"
+      (customize-set-variable 'fountain-hide-emphasis-delim
+                              (not fountain-hide-emphasis-delim))
+      :style toggle
+      :selected fountain-hide-emphasis-delim]
+     ["Hide Syntax Characters"
+      (customize-set-variable 'fountain-hide-syntax-chars
+                              (not fountain-hide-syntax-chars))
+      :style toggle
+      :selected fountain-hide-syntax-chars])
     ["Display Elements Auto-Aligned"
      (customize-set-variable 'fountain-align-elements
                              (not fountain-align-elements))
@@ -5110,21 +5101,17 @@
                       fountain-pages-show-in-mode-line
                       fountain-hide-emphasis-delim
                       fountain-hide-syntax-chars
+                      fountain-shift-all-elements
                       font-lock-maximum-decoration
                       fountain-export-page-size
                       fountain-export-include-title-page
                       fountain-export-scene-heading-format))
-      (if (customize-mark-to-save option)
-          (setq unsaved t)))
-    (if unsaved (custom-save-all))))
+      (when (customize-mark-to-save option) (setq unsaved t)))
+    (when unsaved (custom-save-all))))
 
 
 ;;; Mode Definition
 
-(defvar fountain-mode-syntax-table
-  (make-syntax-table)
-  "Syntax table for `fountain-mode'.")
-
 ;;;###autoload
 (add-to-list 'auto-mode-alist '("\\.fountain\\'" . fountain-mode))
 
@@ -5133,18 +5120,22 @@
   "Major mode for screenwriting in Fountain markup."
   :group 'fountain
   (fountain-init-vars)
-  (hack-local-variables)
   (face-remap-add-relative 'default 'fountain)
   (add-hook 'post-command-hook #'fountain-set-edit-line nil t)
   (add-hook 'post-command-hook #'fountain-auto-upcase-deactivate-maybe nil t)
   (add-hook 'post-self-insert-hook #'fountain-auto-upcase nil t)
-  (if fountain-patch-emacs-bugs (fountain-patch-emacs-bugs))
+  (when fountain-patch-emacs-bugs (fountain-patch-emacs-bugs))
   (jit-lock-register #'fountain-redisplay-scene-numbers)
+  ;; FIXME: Merge those two functions into one (and move them to
+  ;; *-change-functions)
   (jit-lock-register #'fountain-completion-update-scene-headings)
   (jit-lock-register #'fountain-completion-update-characters)
   (fountain-init-mode-line)
-  (fountain-restart-page-count-timer)
-  (fountain-outline-hide-level fountain-outline-startup-level t))
+  (fountain-restart-page-count-timer))
+
+;;;; ChangeLog:
+
+
 
 (provide 'fountain-mode)