From 22758e8c9214f2086fe0c0a424c993f0a52a5780 Mon Sep 17 00:00:00 2001 From: Amin Bandali Date: Thu, 19 May 2022 21:57:45 -0400 Subject: Add ffs (form feed slides) mode for GNU Emacs This is what I used for preparing and presenting my LibrePlanet 2022 talk, 'The Net beyond the web' back in March. :) --- .emacs.d/lisp/ffs/ffsanim.el | 267 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 267 insertions(+) create mode 100644 .emacs.d/lisp/ffs/ffsanim.el (limited to '.emacs.d/lisp/ffs/ffsanim.el') diff --git a/.emacs.d/lisp/ffs/ffsanim.el b/.emacs.d/lisp/ffs/ffsanim.el new file mode 100644 index 0000000..cbf2969 --- /dev/null +++ b/.emacs.d/lisp/ffs/ffsanim.el @@ -0,0 +1,267 @@ +;;; ffsanim.el --- Form Feed Slides animate -*- lexical-binding: t; -*- + +;; Copyright (C) 2022 Amin Bandali + +;; Author: Amin Bandali +;; Version: 0.1.5 +;; Keywords: outlines, tools + +;; 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 +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; A simple mode for doing simple plain text presentations where the +;; slides are separated using the form feed character ( ). Uses +;; animate.el to animate each slide. + +;; Configuration: TODO + +;; Usage: + +;; (add-to-list 'load-path (b/lisp "ffs")) +;; (run-with-idle-timer 0.5 nil #'require 'ffsanim) +;; (with-eval-after-load 'ffsanim +;; (defvar b/original-default-height) +;; (defvar b/ffsanim-default-height 300) +;; (global-set-key +;; (kbd "C-c f s") +;; (lambda () +;; (interactive) +;; (setq +;; b/original-default-height (face-attribute 'default :height)) +;; (set-face-attribute +;; 'default nil :height b/ffsanim-default-height) +;; (message " ") +;; (ffsanim))) +;; (define-key +;; ffsanim-mode-map (kbd "q") +;; (lambda () +;; (interactive) +;; (quit-window) +;; (set-face-attribute +;; 'default nil :height b/original-default-height) +;; (message " ")))) + +;;; Code: + +(require 'animate) + +(defgroup ffsanim nil + "Major mode for form feed-separated plain text presentations." + :version "29.1" + :prefix "ffsanim-") + +(defcustom ffsanim-buffer-name "*ffsanim*" + "The name of the ffsanim presentation buffer." + :group 'ffsanim + :type 'string) + +(defcustom ffsanim-edit-buffer-name "*ffsanim-edit*" + "The name of the ffsanim-edit buffer used when editing a slide." + :group 'ffsanim + :type 'string) + +(defvar ffsanim--source-buffer-name "" + "The name of the form feed-separated \"source\" buffer for a +presentation.") + +(defun ffsanim--buffer () + "Get the ffsanim presentation buffer." + (get-buffer-create ffsanim-buffer-name)) + +(defmacro ffsanim-define-move-to-slide (name &optional doc &rest body) + "Define a function for moving to a slide. +Symbol NAME is the name describing the movement. +DOC is the documentation string to use for the function." + (declare (debug (&define name [&optional stringp] def-body)) + (doc-string 2) (indent defun)) + (when (and doc (not (stringp doc))) + ;; `doc' is the first element of `body', not an actual docstring + (push doc body) + (setq doc nil)) + (let* ((sn (symbol-name name)) + (fname (intern (format "ffsanim-%s-slide" (downcase sn))))) + `(defun ,fname () + ,doc + (interactive) + (let ((s (progn + (pop-to-buffer-same-window + (get-buffer ffsanim--source-buffer-name)) + ,@body + (narrow-to-page) + (prog1 (buffer-string) + (widen) + (pop-to-buffer-same-window (ffsanim--buffer))))) + (animation-buffer-name (buffer-name (ffsanim--buffer))) + (inhibit-read-only t)) + (animate-sequence (split-string s "\n") 0))))) + +(defun ffsanim-edit-slide (&optional add-before-or-after) + "Pop to a new buffer to edit a slide. +If ADD-BEFORE-OR-AFTER is nil or not given, we are editing an +existing slide. Otherwise, if it is `add-before' then the new +slide will be added before the current slide, and if it is +`add-after' then the new slide will be added after the current +slide. The logic for handling this is in `ffsanim-edit-done'." + (interactive) + (let* (m + (s (with-current-buffer (get-buffer ffsanim--source-buffer-name) + (setq m major-mode) + (if add-before-or-after ; if we are adding a new slide + "\n" ; start with just a newline + (narrow-to-page) + (prog1 (buffer-string) + (widen)))))) + (pop-to-buffer-same-window + (get-buffer-create ffsanim-edit-buffer-name)) + (funcall m) + (ffsanim-edit-mode 1) + (insert s) + (goto-char (point-min)) + (setq-local ffsanim--new-location add-before-or-after) + (message + (substitute-command-keys "Edit, then use `\\[ffsanim-edit-done]' \ +to apply your changes or `\\[ffsanim-edit-discard]' to discard them.")))) + +(defun ffsanim-edit-discard () + "Discard current ffsanim-edit buffer and return to the presentation." + (interactive) + (let ((buf (current-buffer))) + (quit-windows-on buf) + (kill-buffer buf)) + (pop-to-buffer-same-window (ffsanim--buffer))) + +(defun ffsanim-edit-done () + "Apply the ffsanim-edit changes and return to the presentation." + (interactive) + (let* (f + (str (buffer-string)) + (s (if (string-suffix-p "\n" str) + str + (concat str "\n"))) + (l ffsanim--new-location)) + (with-current-buffer (get-buffer ffsanim--source-buffer-name) + (save-excursion + (cond + ((eq l 'add-before) + (backward-page) + (insert (format "\n%s " s)) + (setq f #'ffsanim-previous-slide)) + ((eq l 'add-after) + (forward-page) + (insert (format "\n%s " s)) + (setq f #'ffsanim-next-slide)) + ((null l) + (narrow-to-page) + (delete-region (point-min) (point-max)) + (insert s) + (widen) + (setq f #'ffsanim-current-slide))))) + (ffsanim-edit-discard) + (funcall f))) + +(defun ffsanim-new-slide-before () + "Add a new slide before the current slide." + (interactive) + (ffsanim-edit-slide 'add-before)) + +(defun ffsanim-new-slide-after () + "Add a new slide after the current slide." + (interactive) + (ffsanim-edit-slide 'add-after)) + +(defvar ffsanim--old-mode-line-format nil + "The value of `mode-line-format' in the ffsanim presentation buffer +before the last call to `ffsanim--toggle-mode-line'.") + +(defun ffsanim--toggle-mode-line () + "Toggle the display of the mode-line in the current buffer." + (interactive) + (if mode-line-format + (setq-local ffsanim--old-mode-line-format mode-line-format + mode-line-format nil) + (setq-local mode-line-format ffsanim--old-mode-line-format + ffsanim--old-mode-line-format nil)) + (redraw-display)) + +(ffsanim-define-move-to-slide previous + "Go to the previous slide." + (backward-page) + (backward-page)) + +(ffsanim-define-move-to-slide next + "Go to the next slide." + (forward-page)) + +(ffsanim-define-move-to-slide current + "Reload and renimate the current slide." + nil) + +(ffsanim-define-move-to-slide first + "Go to the first slide." + (goto-char (point-min))) + +(ffsanim-define-move-to-slide last + "Go to the last slide." + (goto-char (point-max))) + +(define-derived-mode ffsanim-mode special-mode "ffsanim" + "Major mode for form feed-separated plain text presentations." + :group 'ffsanim + :interative nil + (setq-local animate-total-added-delay 0.3) + (show-paren-local-mode -1) + (display-battery-mode -1) + (ffsanim--toggle-mode-line) + (ffsanim-current-slide)) + +(defvar ffsanim-edit-mode-map + (let ((map (make-sparse-keymap))) + (define-key map (kbd "C-c C-k") #'ffsanim-edit-discard) + (define-key map (kbd "C-c C-c") #'ffsanim-edit-done) + map) + "Keymap for `ffsanim-edit-mode'.") + +(define-minor-mode ffsanim-edit-mode + "Minor mode for editing a single ffsanim slide. +When done editing the slide, run \\[ffsanim-edit-done] to apply your +changes, or \\[ffsanim-edit-discard] to discard them." + :group 'ffsanim + :lighter " ffsanim-edit" + :keymap ffsanim-edit-mode-map + (defvar-local ffsanim--new-location nil + "The location where the new slide should be inserted. +See the docstring for `ffsanim-edit-slide' for more details.")) + +(define-key ffsanim-mode-map (kbd "p") #'ffsanim-previous-slide) +(define-key ffsanim-mode-map (kbd "n") #'ffsanim-next-slide) +(define-key ffsanim-mode-map (kbd "DEL") #'ffsanim-previous-slide) +(define-key ffsanim-mode-map (kbd "SPC") #'ffsanim-next-slide) +(define-key ffsanim-mode-map (kbd "g") #'ffsanim-current-slide) +(define-key ffsanim-mode-map (kbd "<") #'ffsanim-first-slide) +(define-key ffsanim-mode-map (kbd ">") #'ffsanim-last-slide) +(define-key ffsanim-mode-map (kbd "e") #'ffsanim-edit-slide) +(define-key ffsanim-mode-map (kbd "O") #'ffsanim-new-slide-before) +(define-key ffsanim-mode-map (kbd "o") #'ffsanim-new-slide-after) +(define-key ffsanim-mode-map (kbd "m") #'ffsanim--toggle-mode-line) + +(defun ffsanim () + "Start an ffsanim presentation with current buffer as source." + (interactive) + (setq ffsanim--source-buffer-name (buffer-name)) + (pop-to-buffer-same-window (ffsanim--buffer)) + (ffsanim-mode)) + +(provide 'ffsanim) +;;; ffsanim.el ends here -- cgit v1.2.3-83-g751a