#+title: ffs: form feed-separated plain text presentations
#+author: Amin Bandali
#+email: bandali@gnu.org
#+language: en
#+options: ':t toc:nil author:t email:t num:t
#+startup: content
#+macro: stable-version 0.2.2
#+macro: release-date 2026-05-21
#+macro: development-version 0.2.3-git
#+macro: kbd (eval (org-texinfo-kbd-macro $1))
#+texinfo_filename: ffs.info
#+texinfo_dir_category: Emacs misc features
#+texinfo_dir_title: FFS: (ffs)
#+texinfo_dir_desc: Simple form feed-separated plain text presentations
#+texinfo_header: @set MAINTAINERSITE @uref{https://kelar.org/~bandali/,maintainer webpage}
#+texinfo_header: @set MAINTAINER Amin Bandali
#+texinfo_header: @set MAINTAINEREMAIL @email{bandali@@gnu.org}
#+texinfo_header: @set MAINTAINERCONTACT @uref{mailto:bandali@@gnu.org,contact the maintainer}

#+texinfo: @insertcopying

This manual, written by Amin Bandali, introduces =ffs= and its user
options, and provides every other piece of information pertinent to
it.

This manual corresponds to =ffs= stable version {{{stable-version}}},
released on {{{release-date}}}.  Any reference to a newer feature
which does not yet form part of the latest tagged commit is marked as
such.

Current development target is {{{development-version}}}.

- Package name (GNU ELPA): [[https://elpa.gnu.org/packages/ffs.html][=ffs=]]
- Official manual: <https://kelar.org/~bandali/gnu/emacs/ffs.html>
- Change log: <https://kelar.org/~bandali/gnu/emacs/ffs-changelog.html>
- Git repository: <https://git.kelar.org/~bandali/ffs>
- Backronyms: fabulous foolproof slides - for freedom's sake -
  ffs flips slides

Dedicated to the loving memory of [[https://kelar.org/~bandali/life/farangis.html][Farangis Yousefinia]].

#+toc: headlines 8 insert TOC here, with eight headline levels

* COPYING
:PROPERTIES:
:COPYING: t
:CUSTOM_ID: copying
:END:

Copyright \copy 2022--2026 Free Software Foundation, Inc.

#+begin_quote
Permission is granted to copy, distribute and/or modify this document
under the terms of the GNU Free Documentation License, Version 1.3 or
any later version published by the Free Software Foundation; with no
Invariant Sections, with the Front-Cover Texts being "A GNU Manual," and
with the Back-Cover Texts as in (a) below.  A copy of the license is
included in the section entitled "GNU Free Documentation License."

(a) The FSF's Back-Cover Text is: "You have the freedom to copy and
modify this GNU manual."
#+end_quote

* Overview
:PROPERTIES:
:CUSTOM_ID: overview
:END:

This package provides a simple presentation mode which can be applied
to any buffer for reading, writing, editing, and presenting slides.
Slides are separated using the ~page-delimiter~, by default the =^L=
character, known as the form feed, which can be inserted using
{{{kbd(C-q C-l)}}}.  Commands are provided for moving between those
pages, aka slides.  These motions work even when narrowing is in
effect (and they preserve it).  Through detailed explanations and
concrete examples, this manual hopes to demonstrate that ~ffs~ is
designed to be simple and flexible, and easy to use and extend.

ffs does not define any global key bindings.  You can define a key
binding for ~ffs-mode~ in the ~global-map~ like so:

#+begin_src elisp
(global-set-key (kbd "C-c f s") #'ffs-mode)
#+end_src

On Emacs 29 or later, you can use the newer keymap functions, for
instance:

#+begin_src elisp
(keymap-global-set "C-c f s" #'ffs-mode)
#+end_src

For convenience, ~ffs~ is provided as an alias for ~ffs-mode~.

Once enabled, ~ffs-mode~ puts the buffer in read-only mode by setting
~buffer-read-only~, and provides convenient key bindings for moving
between, editing, and inserting slides.  These are available in the
~ffs-mode-map~ keymap.

#+findex: ffs-present-mode
#+vindex: ffs-default-face-height
#+vindex: ffs-hide-cursor
#+vindex: ffs-hide-mode-line
#+vindex: ffs-hide-header-line
When ready to present your slides, you can enable ~ffs-present-mode~,
by default bound to the {{{kbd(s)}}} key in ~ffs-mode-map~ to start a
presentation.  Several user options are provided for customizing the
appearance of the buffer during presentations, for using a different
=height= for the ~default~ face (~ffs-default-face-height~), hiding
the cursor (~ffs-hide-cursor~), hiding the mode line
(~ffs-hide-mode-line~), and hiding the header line
(~ffs-hide-header-line~).

* Installation
:PROPERTIES:
:CUSTOM_ID: installation
:END:

** GNU ELPA package
:PROPERTIES:
:CUSTOM_ID: installation-gnu-elpa
:END:

The package is available as =ffs=.  Simply do:

: M-x package-refresh-contents
: M-x package-install

And search for it.

GNU ELPA provides the latest stable release.  Folks who prefer to
follow the development process in order to report bugs or suggest
changes can use the version of the package from the GNU-devel ELPA.

** Manual installation
:PROPERTIES:
:CUSTOM_ID: installation-manual
:END:

Assuming your Emacs files are found in =~/.emacs.d/=, execute the
following commands in a shell:

#+begin_src sh
# Make a directory for local lisp files and packages
mkdir ~/.emacs.d/lisp

# Go to the new directory
cd ~/.emacs.d/lisp/

# Clone the ffs source repository
git clone https://git.kelar.org/~bandali/ffs
#+end_src

Then, in your ~user-init-file~ (=init.el= or equivalent) add:

#+begin_src emacs-lisp
;; Make Elisp files in the directory available
(add-to-list 'load-path "~/.emacs.d/lisp/ffs")
#+end_src

Then restart your Emacs session or simply evaluate that expression
(using {{{kbd(C-x C-e)}}}) so Emacs can find the package.

On Emacs 31 or later, you can use the new User Lisp feature, where
Emacs Lisp files in the directory specified by ~user-lisp-directory~
and its subdirectories will be recursively byte-compiled, scraped for
autoload cookies, and ensured to be in ~load-path~.  So all you need
is to designate =~/.emacs.d/lisp/= as your ~user-lisp-directory~ in
your ~early-init-file~ (=early-init.el=) like so:

#+begin_src elisp
(setq user-lisp-directory (locate-user-emacs-file "lisp/"))
#+end_src

* Sample configuration
:PROPERTIES:
:CUSTOM_ID: sample-config
:END:
#+cindex: Package configuration

~ffs~ tries to make as few opinionated choices as possible
out-of-the-box, following the principle of least surprise.

#+begin_src elisp
(require 'ffs)

;; You can add a global key binding for enabling `ffs-mode'.

(global-set-key (kbd "C-c f s") #'ffs-mode)

;; These user options apply when `ffs-present-mode' is enabled.
;; Their value is buffer-local.

(setq-default
 ffs-hide-cursor t
 ffs-hide-mode-line t
 ffs-hide-header-line t)

;; You can add hooks to `ffs-mode-hook' or `ffs-present-mode-hook' for
;; hooks that are run every time `ffs-mode' or `ffs-present-mode' are
;; enabled and disabled.

;; The following sample hook is run when `ffs-present-mode' is enabled
;; and it disables `flyspell-mode', `show-paren-local-mode', and
;; `display-fill-column-indicator-mode', and re-enables them when
;; `ffs-present-mode' is disabled, to prevent visual distractions
;; from them during presentations.

(add-hook
 'ffs-present-mode-hook
 (lambda ()
   (let ((arg (if ffs-present-mode -1 1)))
     (mapc
      (lambda (mode) (funcall mode arg))
      '(flyspell-mode
        show-paren-local-mode
        display-fill-column-indicator-mode)))))

;; You can also add keys to `ffs-mode-map' for use when `ffs-mode' is
;; enabled.  For keys that may be only relevant during presentations,
;; you can add them to `ffs-present-mode-map'.
#+end_src

* Default key bindings
:PROPERTIES:
:CUSTOM_ID: keybindings
:END:

This section documents the default key bindings of the various keymaps
of ~ffs~.  Care was taken to choose intuitive mnemonic-based keys for
commands familiar from other existing similar keymaps, but you may
override them if you so choose to.

** ~ffs-mode-map~
:PROPERTIES:
:CUSTOM_ID: keybindings-ffs-mode-map
:END:

The ~ffs-mode-map~ is the main keymap of ~ffs-mode~, used both when
preparing slides with ~ffs-mode~ enabled as well as while presenting
them with ~ffs-present-mode~ enabled:

| Command                       | Key binding(s)                                   |
|-------------------------------+--------------------------------------------------|
| ~ffs-goto-previous~           | {{{kbd(p)}}}, {{{kbd(DEL)}}}, {{{kbd([)}}}       |
| ~ffs-goto-next~               | {{{kbd(n)}}}, {{{kbd(SPC)}}}, {{{kbd(])}}}       |
| ~ffs-goto-first~              | {{{kbd(<)}}}                                     |
| ~ffs-goto-last~               | {{{kbd(>)}}}                                     |
| ~ffs-start~                   | {{{kbd(s)}}}                                     |
| ~ffs-stop-or-quit~            | {{{kbd(q)}}}                                     |
| ~ffs-edit~                    | {{{kbd(e)}}}, {{{kbd(C-c ')}}}                   |
| ~ffs-new-above~               | {{{kbd(O)}}}                                     |
| ~ffs-new-below~               | {{{kbd(o)}}}                                     |
| ~ffs-find-speaker-notes-file~ | {{{kbd(S)}}}                                     |
| ~narrow-to-page~              | {{{kbd(N)}}}                                     |
| ~widen~                       | {{{kbd(W)}}}                                     |
| ~undo~                        | {{{kbd(C-_)}}}, {{{kbd(C-/)}}}, {{{kbd(C-x u)}}} |
| ~ffs-toggle-prefix-map~       | {{{kbd(t)}}}                                     |
| ~describe-mode~               | {{{kbd(?)}}}                                     |

** ~ffs-present-mode-map~
:PROPERTIES:
:CUSTOM_ID: keybindings-ffs-present-mode-map
:END:

The ~ffs-present-mode-map~ keymap used when ~ffs-present-mode~ is
active (during presentations) inherits the key bindings of its parent
keymap ~ffs-mode-map~, with the following changes:

| Command            | Key binding(s) |
|--------------------+----------------|
| ~ffs-stop-or-quit~ | {{{kbd(s)}}}   |

** ~ffs-toggle-prefix-map~
:PROPERTIES:
:CUSTOM_ID: keybindings-ffs-toggle-prefix-map
:END:

The ~ffs-toggle-prefix-map~ keymap, by default bound to {{{kbd(t)}}}
in ~ffs-mode-map~, maps the following commands:

| Command                       | Key binding(s) |
|-------------------------------+----------------|
| ~ffs-toggle-echo-progress~    | {{{kbd(e)}}}   |
| ~ffs-toggle-hide-cursor~      | {{{kbd(c)}}}   |
| ~ffs-toggle-hide-mode-line~   | {{{kbd(m)}}}   |
| ~ffs-toggle-hide-header-line~ | {{{kbd(h)}}}   |
| ~ffs-toggle-dark-mode~        | {{{kbd(d)}}}   |

** ~ffs-edit-mode-map~
:PROPERTIES:
:CUSTOM_ID: keybindings-ffs-edit-mode-map
:END:

The ~ffs-edit-mode-map~ keymap for ~ffs-edit-mode~ is used in
~ffs-edit~ buffers, when editing an existing slide or inserting
a new one:

| Command            | Key binding(s)     |
|--------------------+--------------------|
| ~ffs-edit-discard~ | {{{kbd(C-c C-k)}}} |
| ~ffs-edit-done~    | {{{kbd(C-c C-c)}}} |

* Acknowledgements
:PROPERTIES:
:CUSTOM_ID: thanks
:END:
#+cindex: Contributors

~ffs~ is meant to be a collective effort.  Every bit of help matters
and is greatly appreciated.

- Author/maintainer :: Amin Bandali.

- Contributions to code or the manual :: Protesilaos, Stefan Monnier.

- Ideas and/or user feedback :: Protesilaos.

~ffs~ and its author owe a debt of gratitude to Protesilaos for rounds
of code review and feedback for improving and polishing the package in
preparation for submission to GNU ELPA.  You can watch videos of these
sessions on the author's website:

- [[https://kelar.org/~bandali/gnu/emacs/ffs-code-review-prot.html][FFS code review with Protesilaos]]
- [[https://kelar.org/~bandali/gnu/emacs/ffs-emacs-ext-prot.html][FFS code review and Emacs extensibility with Protesilaos]]

Further, inspiration for parts of ~ffs~'s implementation was
gratefully drawn from Protesilaos's [[https://protesilaos.com/emacs/logos][Logos]] package for Emacs.

* GNU Free Documentation License
:PROPERTIES:
:APPENDIX: t
:CUSTOM_ID: fdl
:END:

#+cindex: FDL, GNU Free Documentation License

#+texinfo: @include fdl.texi

* Indices
:PROPERTIES:
:CUSTOM_ID: indices
:END:

** Function index
:PROPERTIES:
:INDEX: fn
:CUSTOM_ID: indices-fn
:END:

** Variable index
:PROPERTIES:
:INDEX: vr
:CUSTOM_ID: indices-vr
:END:

** Concept index
:PROPERTIES:
:INDEX: cp
:CUSTOM_ID: indices-cp
:END:

* COMMENT Local Variables

# Local Variables:
# org-html-postamble-format: (("en" "<hr />
# <p><a href=\"%i\">Org source</a></p>"))
# End:
