Mercurial > hg > dotemacs
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 (("&" "&") - ("<" "<") - (">" ">") - ("\\\\\\*" "*") - ("\\*\\*\\*\\(.+?\\)\\*\\*\\*" "<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 (("&" "&") - ("<" "<") - (">" ">") - ("\"" """) - ("'" "'") - ("\\\\_" "`") - ("\\\\\\*" "*") - ("_\\*\\*\\*\\(.+?\\)\\*\\*\\*_" "</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 (("&" "&") + ("<" "<") + (">" ">") + ("\\\\\\*" "*") + ("\\*\\*\\*\\(.+?\\)\\*\\*\\*" "<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 (("&" "&") + ("<" "<") + (">" ">") + ("\"" """) + ("'" "'") + ("\\\\_" "`") + ("\\\\\\*" "*") + ("_\\*\\*\\*\\(.+?\\)\\*\\*\\*_" "</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)