changeset 181:5ff62f07dd47

fountain-mode: update to version 2.5.3
author Jordi Gutiérrez Hermoso <jordigh@octave.org>
date Sun, 08 Jul 2018 17:16:16 -0400
parents 66d1f51b7df3
children c3bd84985977
files dotemacs.el elpa/fountain-mode-2.4.2/fountain-mode-autoloads.el elpa/fountain-mode-2.4.2/fountain-mode-pkg.el elpa/fountain-mode-2.4.2/fountain-mode.el 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
diffstat 4 files changed, 374 insertions(+), 179 deletions(-) [+]
line wrap: on
line diff
--- a/dotemacs.el
+++ b/dotemacs.el
@@ -420,7 +420,7 @@
      ("gnu" . "http://elpa.gnu.org/packages/"))))
  '(package-selected-packages
    (quote
-    (imenu-list olivetti fountain-mode markdown-mode magit js2-mode yaml-mode web-mode undo-tree puppet-mode nginx-mode json-mode jade-mode idomenu haml-mode goto-last-change flymake-haml elpy dockerfile-mode)))
+    (fountain-mode markdown-mode magit js2-mode yaml-mode web-mode undo-tree puppet-mode nginx-mode json-mode jade-mode idomenu haml-mode goto-last-change flymake-haml elpy dockerfile-mode)))
  '(safe-local-variable-values
    (quote
     ((encoding . utf-8)
rename from elpa/fountain-mode-2.4.2/fountain-mode-autoloads.el
rename to elpa/fountain-mode-2.5.3/fountain-mode-autoloads.el
--- a/elpa/fountain-mode-2.4.2/fountain-mode-autoloads.el
+++ b/elpa/fountain-mode-2.5.3/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" (23186 63957
-;;;;;;  555361 643000))
+;;;### (autoloads nil "fountain-mode" "fountain-mode.el" (23362 31369
+;;;;;;  7313 833000))
 ;;; Generated autoloads from fountain-mode.el
 
 (add-to-list 'auto-mode-alist '("\\.fountain\\'" . fountain-mode))
rename from elpa/fountain-mode-2.4.2/fountain-mode-pkg.el
rename to elpa/fountain-mode-2.5.3/fountain-mode-pkg.el
--- a/elpa/fountain-mode-2.4.2/fountain-mode-pkg.el
+++ b/elpa/fountain-mode-2.5.3/fountain-mode-pkg.el
@@ -1,2 +1,2 @@
 ;;; -*- no-byte-compile: t -*-
-(define-package "fountain-mode" "2.4.2" "Major mode for screenwriting in Fountain markup" '((emacs "24.5")) :commit "e2878da13e7b87a824ebd6c842e9f552369b220c" :url "https://github.com/rnkn/fountain-mode" :keywords '("wp"))
+(define-package "fountain-mode" "2.5.3" "Major mode for screenwriting in Fountain markup" '((emacs "24.5")))
rename from elpa/fountain-mode-2.4.2/fountain-mode.el
rename to elpa/fountain-mode-2.5.3/fountain-mode.el
--- a/elpa/fountain-mode-2.4.2/fountain-mode.el
+++ b/elpa/fountain-mode-2.5.3/fountain-mode.el
@@ -4,8 +4,8 @@
 
 ;; Author: Paul Rankin <hello@paulwrankin.com>
 ;; Keywords: wp
-;; Package-Version: 2.4.2
-;; Version: 2.4.2
+;; Package-Version: 2.5.3
+;; Version: 2.5.3
 ;; Package-Requires: ((emacs "24.5"))
 ;; URL: https://github.com/rnkn/fountain-mode
 
@@ -124,7 +124,7 @@
 ;; Roadmap
 ;; -------
 
-;; See [Milestones](https://github.com/rnkn/fountain-mode/milestones).
+;; See [Roadmap](https://github.com/rnkn/fountain-mode/projects/2).
 
 ;; History
 ;; -------
@@ -134,13 +134,14 @@
 ;; Tips
 ;; ----
 
+;; Ethereum address 0x209C60afd8aF6c61ac4Dbe340d81D4f789DF64D3
 ;; Bitcoin Cash address 19gUvL8YUzDKr5GyiHpYeF31BfQm87xM9L
 
 
 ;;; Code:
 
 (defconst fountain-version
-  "2.4.2")
+  "2.5.3")
 
 (defun fountain-version ()
   "Return `fountain-mode' version."
@@ -282,6 +283,15 @@
 (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-endnotes-window-side
+                        'fountain-endnotes-display-alist "2.5.1")
+
+(make-obsolete-variable 'fountain-endnotes-window-size
+                        'fountain-endnotes-display-alist "2.5.1")
+
 
 ;;; Customization
 
@@ -783,6 +793,11 @@
   :link '(info-link "(emacs) Font Lock")
   :group 'fountain)
 
+(defface fountain
+  '((t nil))
+  "Default base-level face for `fountain-mode' buffers."
+  :group 'fountain-faces)
+
 (defface fountain-action
   '((t nil))
   "Default face for action."
@@ -948,6 +963,9 @@
   (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
+  (setq-local completion-at-point-functions
+              '(fountain-completion-at-point))
   (setq-local font-lock-extra-managed-props
               '(line-prefix wrap-prefix invisible))
   (setq font-lock-multiline 'undecided)
@@ -1230,6 +1248,143 @@
    (t (looking-at fountain-action-regexp) 'action)))
 
 
+;;; Auto-completion
+
+(defvar-local fountain-completion-scene-headings
+  nil
+  "List of scene headings in the current buffer.")
+
+(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.
+
+n.b. The priority value does not equate to the number of lines
+the character has.")
+
+(defun fountain-completion-update-scene-headings (start end)
+  "Update `fountain-completion-scene-headings' between START and END.
+
+Added to `jit-lock-functions'."
+  (goto-char end)
+  (if (fountain-match-scene-heading)
+      (forward-line 1)
+    (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))))
+    (fountain-forward-scene 1)))
+
+(defun fountain-completion-update-characters (start end)
+  "Update `fountain-completion-characters' between START and END.
+
+Added to `jit-lock-functions'."
+  (goto-char end)
+  (if (fountain-match-scene-heading)
+      (forward-line 1)
+    (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))))
+    (fountain-forward-character 1))
+  (setq fountain-completion-characters
+        (sort fountain-completion-characters #'(lambda (a b)
+                                                 (< (cdr b) (cdr a))))))
+
+(defun fountain-completion-get-characters ()
+  "Return candidates for completing character.
+
+First, return second-last speaking character, followed by each
+previously speaking character within scene. After that, return
+characters from `fountain-completion-characters'."
+  (lambda (string pred action)
+    (let (candidates)
+      (save-excursion
+        (save-restriction
+          (widen)
+          (fountain-forward-character 0)
+          (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))))
+            (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))
+      (if (eq action 'metadata)
+          (list 'metadata
+                (cons 'display-sort-function 'identity)
+                (cons 'cycle-sort-function 'identity))
+        (complete-with-action action candidates string pred)))))
+
+(defun fountain-completion-at-point ()
+  "Return completion table for entity at point.
+Trigger completion with `completion-at-point' (\\[completion-at-point]).
+
+Always delimits entity from beginning of line to point. If at a
+scene heading, return `fountain-scene-heading-candidates'. If
+previous line is blank, return result of
+`fountain-completion-get-characters'.
+
+Set `completion-in-region-mode-map' to nil to retain TAB
+keybinding.
+
+Added to `completion-at-point-functions'."
+  (let (completion-in-region-mode-map jit-lock-mode)
+    (list (line-beginning-position)
+          (point)
+          (completion-table-case-fold
+           (cond
+            ((fountain-match-scene-heading)
+             fountain-completion-scene-headings)
+            ((fountain-blank-before-p)
+             (fountain-completion-get-characters)))))))
+
+(defun fountain-completion-update ()
+  "Create new completion candidates for current buffer.
+
+Completion candidates are usually updated automatically with
+`jit-lock-mode', however this command will add completion
+candidates for the entire buffer.
+
+Add to `fountain-mode-hook' to have full completion upon load."
+  (interactive)
+  (setq fountain-completion-scene-headings nil
+        fountain-completion-characters nil)
+  (save-excursion
+    (save-restriction
+      (widen)
+      (fountain-completion-update-scene-headings (point-min) (point-max))
+      (fountain-completion-update-characters (point-min) (point-max))))
+  (message "Completion candidates updated"))
+
+
 ;;; Pages
 
 (defgroup fountain-pages ()
@@ -1266,7 +1421,9 @@
   "Move point to appropriate place to break a page.
 This is usually before point, but may be after if only skipping
 over whitespace."
-  (skip-chars-forward "\n\r\s\t")
+  ;; 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
@@ -1292,7 +1449,7 @@
       (skip-chars-forward "\s\t")
       (if (not (looking-back (sentence-end)
                              (save-excursion
-                               (fountain-forward-character -1)
+                               (fountain-forward-character 0)
                                (point))))
           (forward-sentence -1)
         ;; This may move to character element, or back within dialogue. If
@@ -1335,53 +1492,57 @@
 
 To considerably speed up this function, supply EXPORT-ELEMENTS
 with `fountain-get-export-elements'."
-  (unless n (setq n 1))
-  (while (< 0 n)
-    ;; Pages don't begin with blank space, so skip over any at point.
-    (skip-chars-forward "\n\r\s\t")
-    (forward-line 0)
-    ;; 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))
-      (skip-chars-forward "\n\r\s\t")
-      (forward-line 0))
-    ;; 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 (or (eobp)
-                           (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")      ; FIXME: \r ?
-          (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)
-              (skip-chars-forward "\n\r\s\t")
-              (goto-char (line-beginning-position))))))))
-    (skip-chars-forward "\n\r\s\t")
-    (fountain-goto-page-break-point)
-    (setq n (1- n))))
+  (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.
@@ -1401,7 +1562,7 @@
                (setq i (1+ i))))))
     (skip-chars-forward "\s\t")
     (if (eolp) (forward-line 1))
-    (fill-move-to-break-point (line-beginning-position))))
+    (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.
@@ -2172,6 +2333,7 @@
      :template fountain-export-tex-template
      :string-replace (("%" "\\\\%")
                       ("&" "\\\\&")
+                      ("#" "\\\\#")
                       ("\\$" "\\\\$")
                       ("\\*\\*\\*\\(.+?\\)\\*\\*\\*" "\\\\textbf{\\\\emph{\\1}}")
                       ("\\*\\*\\(.+?\\)\\*\\*" "\\\\textbf{\\1}")
@@ -3410,11 +3572,7 @@
 
 (defcustom fountain-outline-startup-level
   0
-  "Outline level to show when visiting a file.
-
-This can be set on a per-file basis by including in metadata:
-
-\tstartup-level: N"
+  "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)
@@ -3650,6 +3808,43 @@
          (string-width (match-string 2)))
         (t 6)))
 
+(defcustom fountain-pop-up-indirect-windows
+  nil
+  "Non-nil if opening indirect buffers should make a new window."
+  :type 'boolean
+  :group 'fountain)
+
+(defun fountain-outline-to-indirect-buffer ()
+  "Clone section/scene at point to indirect buffer.
+
+Set `fountain-pop-up-indirect-windows' to control how indirect
+buffer windows are opened."
+  (interactive)
+  (let ((pop-up-windows fountain-pop-up-indirect-windows)
+        (base-buffer (buffer-name (buffer-base-buffer)))
+        beg end heading-name target-buffer)
+    (save-excursion
+      (save-restriction
+        (widen)
+        (outline-back-to-heading t)
+        (setq beg (point))
+        (when (or (fountain-match-section-heading)
+                  (fountain-match-scene-heading))
+          (setq heading-name (match-string-no-properties 3)
+                target-buffer (concat base-buffer "-" heading-name))
+          (outline-end-of-subtree)
+          (setq end (point)))))
+    (if (and (get-buffer target-buffer)
+             (with-current-buffer target-buffer
+               (goto-char beg)
+               (and (or (fountain-match-section-heading)
+                        (fountain-match-scene-heading))
+                    (string= heading-name (match-string-no-properties 3)))))
+        (pop-to-buffer target-buffer)
+      (clone-indirect-buffer target-buffer t)
+      (outline-show-all))
+    (narrow-to-region beg end)))
+
 
 ;;; Navigation
 
@@ -3797,7 +3992,7 @@
 support endnotes."
   :group 'fountain)
 
-(defcustom fountain-endnotes-buffer-name
+(defcustom fountain-endnotes-buffer
   "%s<endnotes>"
   "Name of buffer in which to display file endnotes.
 `%s' is replaced with `buffer-name'.
@@ -3806,45 +4001,28 @@
   :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)
 
-(defcustom fountain-endnotes-window-side
-  'right
-  "Preferred side of frame to display endnotes window."
-  :type '(choice (const :tag "Left" left)
-                 (const :tag "Right" right)
-                 (const :tag "Top" top)
-                 (const :tag "Bottom" bottom))
-  :group 'fountain-endnotes)
-
-(defcustom fountain-endnotes-window-size
-  '(0.3 0.25)
-  "Height and width of the endnotes window as a fraction of root window."
-  :type '(list (float :tag "Height")
-               (float :tag "Width"))
-  :group 'fountain-endnotes)
-
-;; (defcustom fountain-endnotes-display-function
-;;   'display-buffer-pop-up-window
-;;   "Buffer display function used to display endnotes."
-;;   :type '(radio (const :tag "Pop-up new window" display-buffer-pop-up-window)
-;;                 (const :tag "Pop-up new frame" display-buffer-pop-up-frame)
-;;                 (const :tag "Show in same window" display-buffer-same-window))
-;;   :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.
-
-The window displayed is a special \"side\" window, which will
-persist even when calling \\[delete-other-windows]."
+buffer."
   (interactive)
   (set-buffer (or (buffer-base-buffer) (current-buffer)))
   (save-excursion
@@ -3853,23 +4031,20 @@
       (goto-char (point-min))
       (let ((beg (if (re-search-forward fountain-end-regexp nil t)
                      (point)))
-            (src (current-buffer))
-            (buf (format fountain-endnotes-buffer-name (buffer-name))))
+            (buffer (current-buffer))
+            (endnotes-buffer (format fountain-endnotes-buffer (buffer-name))))
         (if beg
-            (if (get-buffer-window buf (selected-frame))
-                (delete-windows-on buf (selected-frame))
+            (if (get-buffer-window endnotes-buffer (selected-frame))
+                (delete-windows-on endnotes-buffer (selected-frame))
               (display-buffer-in-side-window
-               (or (get-buffer buf)
-                   (make-indirect-buffer src buf t))
-               (list (cons 'inhibit-same-window t)
-                     (cons 'side fountain-endnotes-window-side)
-                     (cons 'window-height (car fountain-endnotes-window-size))
-                     (cons 'window-width (cadr fountain-endnotes-window-size))))
-              (with-current-buffer buf
+               (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 buf (selected-frame))))
-              (message "Showing `%s' endnotes; %s to hide" src
+                  (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)))))))
@@ -3877,6 +4052,19 @@
 
 ;;; 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.
+
+Added to `post-command-hook'."
+  (setq fountain--edit-line (line-number-at-pos)))
+
 (defcustom fountain-auto-upcase-scene-headings
   t
   "If non-nil, automatically upcase lines matching `fountain-scene-heading-regexp'."
@@ -3892,6 +4080,22 @@
   nil
   "Overlay used for auto-upcasing current line.")
 
+(defcustom fountain-tab-command
+  'fountain-dwim
+  "Command to call when pressing the TAB key."
+  :type '(radio (function-item fountain-dwim)
+                (function-item fountain-outline-cycle)
+                (function-item fountain-toggle-auto-upcase)
+                (function-item completion-at-point))
+  :group 'fountain)
+
+(defun fountain-tab-action (&optional arg)
+  "Simply calls the value of variable `fountain-tab-command'."
+  (interactive "p")
+  (if (help-function-arglist fountain-tab-command)
+      (funcall fountain-tab-command arg)
+    (funcall fountain-tab-command)))
+
 (defun fountain-auto-upcase-make-overlay ()
   "Make the auto-upcase overlay on current line.
 
@@ -3914,12 +4118,27 @@
 Added as hook to `post-command-hook'."
   (when (or deactivate
             (and (integerp fountain--auto-upcase-line)
-                 (/= fountain--auto-upcase-line
-                     (count-lines (point-min) (line-beginning-position)))))
+                 (/= 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))
-    (message "Auto-upcasing disabled")))
+    (message "Auto-upcasing deactivated")))
+
+(defun fountain-toggle-auto-upcase ()
+  "Toggle line auto-upcasing.
+
+Upcase the current line, and continue to upcase inserted
+characters until either disabled, or point moves to a different
+line (by inserting a newline or by point motion).
+
+The auto-upcased line is highlighted with face
+`fountain-auto-upcase-highlight'"
+  (interactive)
+  (if fountain--auto-upcase-line
+      (fountain-auto-upcase-deactivate-maybe t)
+    (setq fountain--auto-upcase-line (line-number-at-pos))
+    (message "Auto-upcasing activated")
+    (fountain-auto-upcase)))
 
 (defun fountain-auto-upcase ()
   "Upcase all or part of the current line contextually.
@@ -3933,63 +4152,44 @@
 Added as hook to `post-self-insert-hook'."
   (cond ((and fountain-auto-upcase-scene-headings
               (fountain-match-scene-heading))
-         (setq fountain--auto-upcase-line
-               (count-lines (point-min) (line-beginning-position)))
+         (unless (and (integerp fountain--auto-upcase-line)
+                      (= fountain--auto-upcase-line (line-number-at-pos)))
+           (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 3) (point))))
         ((and (integerp fountain--auto-upcase-line)
-              (= fountain--auto-upcase-line
-                 (count-lines (point-min) (line-beginning-position))))
+              (= fountain--auto-upcase-line (line-number-at-pos)))
+         (fountain-auto-upcase-make-overlay)
          (fountain-upcase-line))))
 
 (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 (\\[universal-argument] \\[fountain-dwim]) call `fountain-outline-cycle'
-   and pass ARG, e.g. \\[universal-argument] \\[universal-argument] \\[fountain-dwim] is the same as
-   \\[universal-argument] \\[universal-argument] \\[fountain-outline-cycle].
+   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'.
 
-3. Otherwise, upcase the current line and active auto-upcasing.
-   This highlights the current line with face
-   `fountain-auto-upcase-highlight' and will continue to upcase
-   inserted characters until the command is called again
-   (\\[fountain-dwim]) or point moves to a different line (either
-   by inserting a newline or point motion). This allows a
-   flexible style of entering character names. You may press
-   \\[fountain-dwim] before, during or after typing the name to
-   get the same result."
+3. Otherwise, call `fountain-toggle-auto-upcase'."
   (interactive "p")
-  (cond ((< 1 arg)
+  (cond ((and arg (< 1 arg))
          (fountain-outline-cycle arg))
         ((or (fountain-match-section-heading)
              (fountain-match-scene-heading))
          (fountain-outline-cycle))
         ((fountain-match-include)
          (fountain-include-find-file))
-        (fountain--auto-upcase-line
-         (fountain-auto-upcase-deactivate-maybe t))
         (t
-         (setq fountain--auto-upcase-line
-               (count-lines (point-min) (line-beginning-position)))
-         (fountain-auto-upcase-make-overlay)
-         (fountain-upcase-line)
-         (message "Auto-upcasing enabled"))))
+         (fountain-toggle-auto-upcase))))
 
 (defun fountain-upcase-line (&optional arg)
   "Upcase the line.
 If prefixed with ARG, insert `.' at beginning of line to force
 a scene heading."
   (interactive "P")
-  (if arg
-      (save-excursion
-        (forward-line 0)
-        (insert ".")))
+  (if arg (save-excursion (forward-line 0) (insert ".")))
   (upcase-region (line-beginning-position) (line-end-position)))
 
 (defun fountain-upcase-line-and-newline (&optional arg)
@@ -4017,26 +4217,6 @@
             (delete-region x (point))
           (unless (eobp) (forward-char 1)))))))
 
-(defun fountain-insert-alternate-character ()
-  "Insert second-last character within the scene, and newline."
-  (interactive)
-  (let* ((n -1)
-         (character-1 (fountain-get-character n 'scene))
-         (character-2 character-1))
-    (while (and (stringp character-1)
-                (string= character-1 character-2))
-      (setq n (1- n)
-            character-2 (fountain-get-character n 'scene)))
-    (if character-2
-        (let ((x (save-excursion
-                   (skip-chars-backward "\s\n\t")
-                   (point))))
-          (delete-region x (point))
-          (newline 2)
-          (insert character-2))
-      (message "No alternate character within scene"))
-    (newline)))
-
 (defun fountain-insert-synopsis ()
   "Insert synopsis below scene heading of current scene."
   (interactive)
@@ -4689,7 +4869,7 @@
 (defvar fountain-mode-map
   (let ((map (make-sparse-keymap)))
     ;; Editing commands:
-    (define-key map (kbd "TAB") #'fountain-dwim)
+    (define-key map (kbd "TAB") #'fountain-tab-action)
     (define-key map (kbd "C-c RET") #'fountain-upcase-line-and-newline)
     (define-key map (kbd "<S-return>") #'fountain-upcase-line-and-newline)
     (define-key map (kbd "C-c C-c") #'fountain-upcase-line)
@@ -4701,6 +4881,8 @@
     (define-key map (kbd "C-c C-x _") #'fountain-remove-scene-numbers)
     (define-key map (kbd "C-c C-x f") #'fountain-set-font-lock-decoration)
     (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...
     ;; (define-key map (kbd "C-c C-c") #'fountain-include-find-file)
     ;; Navigation commands:
@@ -4725,6 +4907,7 @@
     (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:
@@ -4762,6 +4945,8 @@
      ["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]
+     "---"
      ["Up Heading" fountain-outline-up]
      ["Next Heading" fountain-outline-next]
      ["Previous Heading" fountain-outline-previous]
@@ -4787,11 +4972,11 @@
       (customize-set-variable 'fountain-pages-show-in-mode-line nil)
       :style radio
       :selected (not fountain-pages-show-in-mode-line)]
-     ["In Mode Line with Manual Update"
+     ["Show in Mode Line with Manual Update"
       (customize-set-variable 'fountain-pages-show-in-mode-line 'force)
       :style radio
       :selected (eq fountain-pages-show-in-mode-line 'force)]
-     ["In Mode Line with Automatic Update"
+     ["Show in Mode Line with Automatic Update"
       (customize-set-variable 'fountain-pages-show-in-mode-line 'timer)
       :style radio
       :selected (eq fountain-pages-show-in-mode-line 'timer)])
@@ -4801,19 +4986,20 @@
     ["Insert Note" fountain-insert-note]
     ["Insert Page Break..." fountain-insert-page-break]
     ["Refresh Continued Dialog" fountain-continued-dialog-refresh]
+    ["Update Auto-Completion" fountain-completion-update]
     "---"
     ("Show/Hide"
      ["Endnotes" fountain-show-or-hide-endnotes]
-     ["Emphasis Delimiters"
+     ["Hide Emphasis Delimiters"
       (customize-set-variable 'fountain-hide-emphasis-delim
                               (not fountain-hide-emphasis-delim))
       :style toggle
-      :selected (not fountain-hide-emphasis-delim)]
-     ["Syntax Characters"
+      :selected fountain-hide-emphasis-delim]
+     ["Hide Syntax Characters"
       (customize-set-variable 'fountain-hide-syntax-chars
                               (not fountain-hide-syntax-chars))
       :style toggle
-      :selected (not fountain-hide-syntax-chars)])
+      :selected fountain-hide-syntax-chars])
     ("Syntax Highlighting"
      ["Minimum"
       (fountain-set-font-lock-decoration 1)
@@ -4880,6 +5066,19 @@
      ["Customize Export"
       (customize-group 'fountain-export)])
     "---"
+    ("TAB Command"
+     ["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)
+      :style radio
+      :selected (eq fountain-tab-command 'fountain-outline-cycle)]
+     ["Toggle Auto-Upcasing" (customize-set-variable 'fountain-tab-command 'fountain-toggle-auto-upcase)
+      :style radio
+      :selected (eq fountain-tab-command 'fountain-toggle-auto-upcase)]
+     ["Auto-Completion" (customize-set-variable 'fountain-tab-command 'completion-at-point)
+      :style radio
+      :selected (eq fountain-tab-command 'completion-at-point)])
     ["Display Elements Auto-Aligned"
      (customize-set-variable 'fountain-align-elements
                              (not fountain-align-elements))
@@ -4920,15 +5119,12 @@
     (if unsaved (custom-save-all))))
 
 
-;;; Syntax Table
+;;; Mode Definition
 
 (defvar fountain-mode-syntax-table
   (make-syntax-table)
   "Syntax table for `fountain-mode'.")
 
-
-;;; Mode Definition
-
 ;;;###autoload
 (add-to-list 'auto-mode-alist '("\\.fountain\\'" . fountain-mode))
 
@@ -4937,16 +5133,15 @@
   "Major mode for screenwriting in Fountain markup."
   :group 'fountain
   (fountain-init-vars)
-  (let ((n (plist-get (fountain-read-metadata) 'startup-level)))
-    (if (stringp n)
-        (setq-local fountain-outline-startup-level
-                    (min (string-to-number n) 6))))
-  (add-hook 'post-self-insert-hook
-            #'fountain-auto-upcase nil t)
-  (add-hook 'post-command-hook
-            #'fountain-auto-upcase-deactivate-maybe nil t)
+  (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))
-  (jit-lock-register #'fountain-redisplay-scene-numbers t)
+  (jit-lock-register #'fountain-redisplay-scene-numbers)
+  (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))