summaryrefslogtreecommitdiffstats
path: root/.emacs.d/lisp/ffs/ffsanim.el
blob: cbf29696106e224328748683dac2060966378e27 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
;;; ffsanim.el --- Form Feed Slides animate -*- lexical-binding: t; -*-

;; Copyright (C) 2022  Amin Bandali <bandali@gnu.org>

;; Author: Amin Bandali <bandali@gnu.org>
;; 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 <https://www.gnu.org/licenses/>.

;;; 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