-
Notifications
You must be signed in to change notification settings - Fork 14
/
pandoc-mode.el
1606 lines (1438 loc) · 70.8 KB
/
pandoc-mode.el
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
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
;;; pandoc-mode.el --- Minor mode for interacting with Pandoc -*- lexical-binding: t -*-
;; Copyright (c) 2009-2022 Joost Kremers
;; Author: Joost Kremers <[email protected]>
;; Maintainer: Joost Kremers <[email protected]>
;; Created: 31 Oct 2009
;; Version: 2.33
;; Keywords: text, pandoc
;; URL: http://joostkremers.github.io/pandoc-mode/
;; Package-Requires: ((hydra "0.10.0") (dash "2.10.0"))
;; Redistribution and use in source and binary forms, with or without
;; modification, are permitted provided that the following conditions
;; are met:
;;
;; 1. Redistributions of source code must retain the above copyright
;; notice, this list of conditions and the following disclaimer.
;; 2. Redistributions in binary form must reproduce the above copyright
;; notice, this list of conditions and the following disclaimer in the
;; documentation and/or other materials provided with the distribution.
;; 3. The name of the author may not be used to endorse or promote products
;; derived from this software without specific prior written permission.
;;
;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
;; IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
;; OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
;; IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
;; INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
;; NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES ; LOSS OF USE,
;; DATA, OR PROFITS ; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
;; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
;; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
;; THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
;;; Commentary:
;; Pandoc-mode is a minor mode for interacting with Pandoc, a 'universal
;; document converter': <http://johnmacfarlane.net/pandoc/>.
;;
;; See the pandoc-mode manual for usage and installation instructions.
;;; Code:
(require 'easymenu)
(require 'hydra)
(require 'dash)
(require 'pandoc-mode-utils)
(require 'cl-lib)
(require 'thingatpt)
(declare-function ebib "ext:ebib.el" (&optional file key))
(defvar-local pandoc--@-counter 0 "Counter for (@)-lists.")
(defvar pandoc--window-config nil
"Stores the window configuration before calling pandoc--select-@.")
(defvar pandoc--pre-select-buffer nil
"Buffer from which pandoc--@-select is called.")
(defvar pandoc--@-buffer nil
"Buffer for selecting an (@)-element.")
(defvar pandoc--@-overlay nil
"Overlay for pandoc--@-buffer.")
(defun pandoc--@-counter-inc ()
"Increment pandoc--@-counter and return the new value."
(when (= pandoc--@-counter 0) ; hasn't been updated in this buffer yet.
(save-excursion
(goto-char (point-min))
(while (re-search-forward "(@\\([0-9]+?\\))" (point-max) t)
(let ((label (string-to-number (match-string 1))))
(when (> label pandoc--@-counter)
(setq pandoc--@-counter label))))))
(setq pandoc--@-counter (1+ pandoc--@-counter)))
(defvar pandoc-@-mode-map
(let ((map (make-sparse-keymap)))
(define-key map "q" #'pandoc-quit-@-select)
(define-key map "j" #'pandoc-next-@)
(define-key map "n" #'pandoc-next-@)
(define-key map [down] #'pandoc-next-@)
(define-key map "k" #'pandoc-prev-@)
(define-key map "p" #'pandoc-prev-@)
(define-key map [up] #'pandoc-prev-@)
(define-key map [return] #'pandoc-select-current-@)
(define-key map [home] #'pandoc-goto-first-@)
(define-key map [prior] #'pandoc-goto-first-@)
(define-key map [end] #'pandoc-goto-last-@)
(define-key map [next] #'pandoc-goto-first-@)
map)
"Keymap for pandoc-@-mode.")
(defun pandoc-quit-@-select ()
"Leave pandoc--@-select-buffer without selecting an (@)-label."
(interactive)
(remove-overlays)
(set-window-configuration pandoc--window-config)
(switch-to-buffer pandoc--pre-select-buffer))
(defun pandoc-next-@ ()
"Highlight next (@)-definition."
(interactive)
(if (= (count-lines (point) (point-max)) 2)
(beep)
(forward-line 2)
(move-overlay pandoc--@-overlay (point) (line-end-position))))
(defun pandoc-prev-@ ()
"Highlight previous (@)-definition."
(interactive)
(if (= (point) (point-min))
(beep)
(forward-line -2)
(move-overlay pandoc--@-overlay (point) (line-end-position))))
(defun pandoc-goto-first-@ ()
"Highlight the first (@)-definition."
(interactive)
(goto-char (point-min))
(move-overlay pandoc--@-overlay (point) (line-end-position)))
(defun pandoc-goto-last-@ ()
"Highlight the last (@)-definition."
(interactive)
(goto-char (point-max))
(forward-line -2)
(move-overlay pandoc--@-overlay (point) (line-end-position)))
(defun pandoc-select-current-@ ()
"Leave pandoc--@-select-buffer and insert selected (@)-label at point."
(interactive)
(looking-at " \\((@.*?)\\)")
(let ((label (match-string 1)))
(remove-overlays)
(set-window-configuration pandoc--window-config)
(switch-to-buffer pandoc--pre-select-buffer)
(insert label)))
(define-derived-mode pandoc-@-mode
fundamental-mode "Pandoc-select"
"Major mode for the Pandoc-select buffer."
(setq buffer-read-only t)
(setq truncate-lines t))
(defvar pandoc-mode-map
(let ((map (make-sparse-keymap)))
(define-key map "\C-c/" #'pandoc-main-hydra/body)
map)
"Keymap for pandoc-mode.")
;;;###autoload
(define-minor-mode pandoc-mode
"Minor mode for interacting with Pandoc."
:init-value nil :lighter (:eval (concat " Pandoc/" (pandoc--get 'write))) :global nil
(cond
(pandoc-mode ; pandoc-mode is turned on
(setq pandoc--local-settings (copy-tree pandoc--options))
(pandoc--set 'read (cdr (assq major-mode pandoc-major-modes)))
(setq pandoc--settings-modified-flag nil)
;; Make sure the output buffer exists.
(get-buffer-create pandoc--output-buffer-name)
(pandoc-faces-load))
((not pandoc-mode) ; pandoc-mode is turned off
(setq pandoc--local-settings nil
pandoc--settings-modified-flag nil)
(pandoc-faces-unload))))
;;;###autoload
(defun conditionally-turn-on-pandoc ()
"Turn on pandoc-mode if a pandoc settings file exists.
This is for use in major mode hooks."
(when (and (buffer-file-name)
(file-exists-p (pandoc--create-settings-filename 'local (buffer-file-name) "default")))
(pandoc-mode 1)))
(defun pandoc-toggle-extension (extension rw)
"Toggle the value of EXTENSION.
RW is either `read' or `write', indicating whether the extension
should be toggled for the input or the output format."
(interactive (list (completing-read "Extension: " pandoc--extensions nil t)
(intern (completing-read "Read/write: " '("read" "write") nil t))))
(let* ((current-value (pandoc--get-extension extension rw))
(new-value (cond
((memq current-value '(+ -)) ; if the value is set explicitly
nil) ; we can simply return it to the default
((pandoc--extension-in-format-p extension (pandoc--get rw) rw) ; if the extension is part of the current format
'-) ; we explicitly unset it
(t '+)))) ; otherwise we explicitly set it
(pandoc--set-extension extension rw new-value)))
(defun pandoc-toggle-read-extension-by-number (n)
"Toggle a `read' extension.
N is the index of the extension in `pandoc--extensions'."
(interactive "P")
(let* ((ext (caar (nthcdr (1- n) pandoc--extensions))))
(pandoc-toggle-extension ext 'read)))
(defun pandoc-toggle-write-extension-by-number (n)
"Toggle a `write' extension.
N is the index of the extension in `pandoc--extensions'."
(interactive "P")
(let* ((ext (caar (nthcdr (1- n) pandoc--extensions))))
(pandoc-toggle-extension ext 'write)))
(defun pandoc--create-settings-filename (type filename output-format)
"Create a settings filename.
TYPE is the type of settings file, either `local' or `project'.
FILENAME is name of the file for which the settings file is to be
created, OUTPUT-FORMAT the output format of the settings file,
which is recorded in its name. The return value is an absolute
filename."
(setq filename (expand-file-name filename))
(cond
((eq type 'local)
(concat (file-name-directory filename) "." (file-name-nondirectory filename) "." output-format ".pandoc"))
((eq type 'project)
(concat (file-name-directory filename) "Project." output-format ".pandoc"))))
(defun pandoc--create-global-settings-filename (format)
"Create a global settings filename.
FORMAT is the output format to use."
(concat (file-name-as-directory pandoc-data-dir) format ".pandoc"))
(defun pandoc--compose-output-file-name (&optional pdf input-file)
"Create an output file name for the current buffer based on INPUT-FILE.
If INPUT-FILE is non-nil, use the file the current buffer is
visiting. If the current buffer's output file is set to t, or if
the target format is odt, epub or docx, create an output file
name based on INPUT-FILE. If an output directory is set, use
this directory, otherwise use the directory of INPUT-FILE (which
should be a fully qualified file path).
If an explicit output file is set, use that file, combined with
the output directory, if given. If an output file name is set
but no output directory, use the directory of INPUT-FILE.
If PDF is non-nil, use `pdf' as the extension.
If the current buffer's settings do not specify an output
file (i.e., if the output file is set to nil), return nil."
(or input-file
(setq input-file (expand-file-name (buffer-file-name))))
(cond
((or (eq (pandoc--get 'output) t) ; If the user set the output file to T
(and (null (pandoc--get 'output)) ; or if the user set no output file but either
(or pdf ; (i) we're converting to pdf, or
(member (pandoc--get 'write) ; (ii) the output format is odt, epub or docx
'("odt" "epub" "docx" "pptx")))))
(format "%s/%s%s" ; we create an output file name.
(expand-file-name (or (pandoc--get 'output-dir)
(file-name-directory input-file)))
(file-name-sans-extension (file-name-nondirectory input-file))
(if pdf
".pdf"
(cadr (assoc (pandoc--get 'write) pandoc-output-format-extensions)))))
((stringp (pandoc--get 'output)) ; If the user set an output file,
(format "%s/%s" ; we combine it with the output directory
(expand-file-name (or (pandoc--get 'output-dir)
(file-name-directory input-file)))
(if pdf ; and check if we're converting to pdf.
(concat (file-name-sans-extension (pandoc--get 'output)) ".pdf")
(pandoc--get 'output))))
(t nil)))
(defun pandoc--format-all-options (output-file &optional pdf)
"Create a list of strings with pandoc options for the current buffer.
OUTPUT-FILE the name of the output file. If PDF is non-nil, an
output file is always set, which gets the suffix `.pdf'. If the
output format is \"odt\", \"epub\" or \"docx\" but no output file
is specified, one will be created."
(let ((read (format "--read=%s%s%s" (pandoc--get 'read) (if (pandoc--get 'read-lhs) "+lhs" "")
(pandoc--format-extensions (pandoc--get 'read-extensions))))
(write (if pdf
(if (member (pandoc--get 'write) pandoc--pdf-able-formats)
(format "--write=%s" (pandoc--get 'write))
"--write=latex")
(format "--write=%s%s%s" (pandoc--get 'write) (if (pandoc--get 'write-lhs) "+lhs" "")
(pandoc--format-extensions (pandoc--get 'write-extensions)))))
(output (when output-file (format "--output=%s" output-file)))
;; Filters are handled separately, because they sometimes need to be
;; passed to `pandoc' before other options.
(filters (pandoc--format-list-options 'filter (pandoc--get 'filter)))
(lua-filters (pandoc--format-list-options 'lua-filter (pandoc--get 'lua-filter)))
(list-options (mapcar (lambda (option)
(pandoc--format-list-options option (pandoc--get option)))
(remove 'lua-filter
(remove 'filter pandoc--list-options))))
(alist-options (mapcar (lambda (option)
(pandoc--format-alist-options option (pandoc--get option)))
pandoc--alist-options))
(cli-options (pandoc--format-cli-options)))
;; Note: list-options and alist-options are both lists of lists, so we need to flatten them first.
(delq nil (append (list read write output) filters lua-filters cli-options (apply #'append list-options) (apply #'append alist-options)))))
(defun pandoc--format-extensions (extensions)
"Create a string of extensions to be added to the Pandoc command line.
EXTENSIONS is an alist of (<extension> . <value>) pairs."
(mapconcat (lambda (elt)
(if (cdr elt)
(format "%s%s" (cdr elt) (car elt))
""))
extensions
""))
(defun pandoc--format-list-options (option values)
"Create a list of cli options for OPTION from the values in VALUES."
(mapcar (lambda (value)
(format "--%s=%s" option (if (eq (get option 'pandoc-list-type) 'file)
(pandoc--expand-absolute-path value)
value)))
values))
(defun pandoc--format-alist-options (option alist)
"Create a list of cli options for OPTION from the key-value pairs in ALIST."
(mapcar (lambda (kv)
(let ((key (car kv))
(value (cdr kv)))
;; if key or value contains a colon, we use the short form
;; of the option, because it uses = to separate the two.
(if (or (string-match-p ":" key)
(string-match-p ":" value))
;; the only two alist options are `variable' and
;; `metadata', whose short forms are `V' and `M',
;; respectively, so we can just capitalise their first
;; letters.
(format "-%c %s%s" (upcase (aref (symbol-name option) 0))
key
(if (eq value t)
""
(format "=%s" value)))
(format "--%s=%s%s" option key
(if (eq value t)
""
(format ":%s" value))))))
alist))
(defun pandoc--format-cli-options ()
"Create a list of options in `pandoc--cli-options'."
(mapcar (lambda (option)
(let ((value (pandoc--get option)))
(when (and value
(memq option pandoc--filepath-options))
(setq value (pandoc--expand-absolute-path value)))
(cond
((eq value t) (format "--%s" option))
((or (numberp value)
(stringp value)) (format "--%s=%s" option value))
(t nil))))
pandoc--cli-options))
(defun pandoc-process-directives (output-format)
"Processes pandoc-mode @@-directives in the current buffer.
OUTPUT-FORMAT is passed unchanged to the functions associated
with the @@-directives."
(interactive (list (pandoc--get 'write)))
(mapc #'funcall pandoc-directives-hook)
(let ((case-fold-search nil)
(directives-regexp (concat "\\([\\]?\\)@@" (regexp-opt (mapcar #'car pandoc-directives) t))))
(goto-char (point-min))
(while (re-search-forward directives-regexp nil t)
(if (string= (match-string 1) "\\")
(delete-region (match-beginning 1) (match-end 1))
(let ((@@-beg (match-beginning 0))
(@@-end (match-end 0))
(directive-fn (cdr (assoc (match-string 2) pandoc-directives))))
(cond
((eq (char-after) ?{) ; if there is an argument.
;; note: point is on the left brace, and scan-lists
;; returns the position *after* the right brace. we need
;; to adjust both values to get the actual argument.
(let* ((arg-beg (1+ (point)))
(arg-end (1- (scan-lists (point) 1 0)))
(text (buffer-substring-no-properties arg-beg arg-end)))
(goto-char @@-beg)
(delete-region @@-beg (1+ arg-end))
(insert (funcall directive-fn output-format text)))
(goto-char @@-beg))
;; check if the next character is not a letter or number.
;; if it is, we're actually on a different directive.
((looking-at "[a-zA-Z0-9]") t)
;; otherwise there is no argument.
(t (goto-char @@-beg)
(delete-region @@-beg @@-end) ; else there is no argument
(insert (funcall directive-fn output-format))
(goto-char @@-beg))))))))
(defun pandoc--process-lisp-directive (_ lisp)
"Process @@lisp directives.
The first argument is the output argument and is ignored. LISP
is the argument of the @@lisp directive."
(format "%s" (eval (car (read-from-string lisp)))))
(defun pandoc--process-include-directive (_ include-file)
"Process @@include directives.
The first argument is the output argument and is ignored.
INCLUDE-FILE is the argument of the @@include directive."
(with-temp-buffer
(insert-file-contents include-file)
(buffer-string)))
(defun pandoc--get-file-local-options (buffers)
"Return all pandoc related file-local variables and their values.
These are file local variables beginning with `pandoc/'. Return
value is an alist of (var . value) pairs. The values are
searched in BUFFERS, which is a list of buffers. The first value
found for a particular value is the one returned. In other
words, a value from a buffer earlier in BUFFERS overrides the
value of a later buffer."
(delq nil (mapcar (lambda (option)
(let ((var (intern (concat "pandoc/" (symbol-name (car option)))))
(bs buffers))
(while (and bs (not (local-variable-p var (car bs))))
(setq bs (cdr bs)))
(when bs
(cons var (buffer-local-value var (car bs))))))
pandoc--options)))
;; `pandoc-call-external' sets up a process sentinel that needs to refer to
;; `pandoc-binary' to provide an informative message. We want to allow a
;; buffer-local value of `pandoc-binary', but the process sentinel doesn't
;; have the necessary context. With `lexical-binding' set to t, we could
;; make the sentinel a closure, but this only works for Emacs >= 24.1. An
;; alternative way is to use a global variable, which, however, means that
;; we can only have one pandoc subprocess at a time. Hopefully that won't
;; be a problem.
(defvar pandoc--local-binary "pandoc"
"Temporary store for the buffer-local value of `pandoc-binary'.")
(defun pandoc--call-external (output-format &optional pdf region)
"Call pandoc on the current buffer.
This function creates a temporary buffer and sets up the required
local options. The contents of the current buffer is copied into
the temporary buffer, the @@-directives are processed, after
which pandoc is called.
OUTPUT-FORMAT is the format to use. If t, the current buffer's
output format is used. If PDF is non-nil, a pdf file is created.
REGION is a cons cell specifying the beginning and end of the
region to be sent to pandoc.
If the current buffer's \"master file\" option is set, that file
is processed instead. The output format is taken from the current
buffer, however, unless one is provided specifically. REGION is
also ignored in this case."
(let* ((orig-buffer (current-buffer))
(buffer (if (pandoc--get 'master-file)
(find-file-noselect (pandoc--get 'master-file))
(current-buffer)))
(input-file (buffer-file-name buffer))
output-file
(display-name (buffer-name)))
;; If the buffer is visiting a file, we want to display the file name in
;; messages. If the buffer is not visiting a file, we create a file name in
;; case we need one, but we display the buffer name in messages. Then
;; create the output file name.
(if input-file
(setq display-name (file-name-nondirectory input-file))
(setq input-file (expand-file-name (concat "./" (pandoc--create-file-name-from-buffer (buffer-name))))))
;; If there's a master file, ignore the region.
(if (pandoc--get 'master-file)
(setq region nil))
;; Keep track of the buffer-local value of `pandoc-binary', if there is one.
(setq pandoc--local-binary (buffer-local-value 'pandoc-binary buffer))
;; We use a temp buffer, so we can process @@-directives without having to
;; undo them and set the options independently of the original buffer.
(with-temp-buffer
(cond
;; If an output format was provided, try and load a settings file for it.
((stringp output-format)
(unless (and input-file
(pandoc--load-settings-for-file (expand-file-name input-file) output-format t))
;; If no settings file was found, unset all options except input and output format.
(setq pandoc--local-settings (copy-tree pandoc--options))
(pandoc--set 'write output-format)
(pandoc--set 'read (pandoc--get 'read buffer))))
;; If no output format was provided, we use BUFFER's options, except the
;; output format, which we take from ORIG-BUFFER. We also set the local
;; variable `output-format' to this format, so that the value of
;; `pandoc--latest-run' is set correctly beloww.
((eq output-format t)
(setq pandoc--local-settings (buffer-local-value 'pandoc--local-settings buffer))
(pandoc--set 'write (setq output-format (pandoc--get 'write orig-buffer)))))
;; Set the name of the output file.
(setq output-file (pandoc--compose-output-file-name pdf input-file))
;; Copy any local `pandoc/' variables from `orig-buffer' or
;; `buffer' (the values in `orig-buffer' take precedence):
(dolist (option (pandoc--get-file-local-options (list orig-buffer buffer)))
(set (make-local-variable (car option)) (cdr option)))
(let ((option-list (pandoc--format-all-options output-file pdf)))
(insert-buffer-substring-no-properties buffer (car region) (cdr region))
(insert "\n") ; Insert a new line. If Pandoc does not encounter a newline on a single line, it will hang forever.
(message "Running %s on %s" (file-name-nondirectory pandoc--local-binary) display-name)
(pandoc-process-directives (pandoc--get 'write))
(with-current-buffer (get-buffer-create pandoc--output-buffer-name)
(erase-buffer))
(pandoc--log 'log "%s\n%s" (make-string 50 ?=) (current-time-string))
(pandoc--log 'log "Calling %s with:\n\n%s %s" (file-name-nondirectory pandoc--local-binary) pandoc--local-binary (mapconcat #'identity option-list " "))
(let ((coding-system-for-read 'utf-8)
(coding-system-for-write 'utf-8)
(log-success (lambda (file binary)
(pandoc--log 'message "%s: %s finished successfully"
(file-name-nondirectory file)
(file-name-nondirectory binary))
(with-current-buffer orig-buffer
(setq pandoc--latest-run (cons output-format output-file)))))
(log-failure (lambda (file binary)
(pandoc--log 'message "%s: Error in %s process"
(file-name-nondirectory file)
(file-name-nondirectory binary))
(with-current-buffer orig-buffer
(setq pandoc--latest-run 'error)))))
(cond
(pandoc-use-async
(let* ((process-connection-type pandoc-process-connection-type)
(process (apply #'start-process "pandoc-process" (get-buffer-create pandoc--output-buffer-name) pandoc--local-binary option-list)))
(set-process-sentinel process (lambda (_ e)
(cond
((string-equal e "finished\n")
(funcall log-success display-name pandoc--local-binary)
(run-hooks 'pandoc-async-success-hook))
(t (funcall log-failure display-name pandoc--local-binary)
(display-buffer pandoc--output-buffer-name)))))
(process-send-region process (point-min) (point-max))
(process-send-eof process)))
((not pandoc-use-async)
(if (= 0 (apply #'call-process-region (point-min) (point-max) pandoc--local-binary nil (get-buffer-create pandoc--output-buffer-name) t option-list))
(funcall log-success display-name pandoc--local-binary)
(funcall log-failure display-name pandoc--local-binary)
(display-buffer pandoc--output-buffer-name)))))))))
(defun pandoc-run-pandoc (&optional prefix)
"Run pandoc on the current document.
If called with a PREFIX argument, the user is asked for an output
format. Otherwise, the output format currently set in the buffer
is used.
If the region is active, pandoc is run on the region instead of
the buffer."
(interactive "P")
(pandoc--call-external (if prefix
(completing-read "Output format to use: "
(pandoc--extract-formats 'output)
nil t)
t)
nil
(if (use-region-p)
(cons (region-beginning) (region-end)))))
(defvar-local pandoc--output-format-for-pdf nil
"Output format used to for pdf conversion.
Set the first time the user converts to pdf. Unset when the
user changes output format.")
(defun pandoc-convert-to-pdf (&optional prefix)
"Convert the current document to pdf.
If the output format of the current buffer can be used to create
a pdf (latex, context, or html5), the buffer's options are used.
If not, the user is asked to supply a format. If a settings file
for the user-supplied format exists, the settings from this file
are used for conversion. If no such settings file exists, only
the input and output format are set, all other options are unset.
This user-supplied output format is persistent: the next pdf
conversion uses the same format.
If called with a PREFIX argument \\[universal-argument], always ask the user for a
pdf-able format.
Note that if the user changes the output format for the buffer,
the format for pdf conversion is unset.
If the region is active, pandoc is run on the region instead of
the buffer (except when a master file is set, in which case
pandoc is always run on the master file)."
;; TODO When the region is active, it might be nice to run pandoc on the
;; region but use the master file's settings.
(interactive "P")
(let ((ask (and (listp prefix) (eq (car prefix) 4))))
(cond
((and (not ask)
(member (pandoc--get 'write) pandoc--pdf-able-formats))
(setq pandoc--output-format-for-pdf t)) ; Use buffer's output format and settings.
((or ask
(not pandoc--output-format-for-pdf))
(setq pandoc--output-format-for-pdf (completing-read "Specify output format for pdf creation: " pandoc--pdf-able-formats nil t nil nil (car pandoc--pdf-able-formats))))))
(pandoc--call-external pandoc--output-format-for-pdf t (when (use-region-p) (cons (region-beginning) (region-end)))))
(defun pandoc-set-default-format ()
"Set the current output format as default.
This is done by creating a symbolic link to the relevant settings
files. (Therefore, this function is not available on Windows.)"
(interactive)
(if (eq system-type 'windows-nt)
(message "This option is not available on MS Windows")
(let ((current-settings-file
(file-name-nondirectory (pandoc--create-settings-filename 'local (buffer-file-name)
(pandoc--get 'write))))
(current-project-file
(file-name-nondirectory (pandoc--create-settings-filename 'project (buffer-file-name)
(pandoc--get 'write)))))
(when (not (file-exists-p current-settings-file))
(pandoc--save-settings 'local (pandoc--get 'write)))
(make-symbolic-link current-settings-file
(pandoc--create-settings-filename 'local (buffer-file-name) "default") t)
(when (file-exists-p current-project-file)
(make-symbolic-link current-project-file
(pandoc--create-settings-filename 'project (buffer-file-name) "default") t))
(message "`%s' set as default output format." (pandoc--get 'write)))))
(defun pandoc-save-settings-file ()
"Save the settings of the current buffer.
This function just calls pandoc--save-settings with the
appropriate output format."
(interactive)
(pandoc--save-settings 'local (pandoc--get 'write)))
(defun pandoc-save-project-file ()
"Save the current settings as a project file."
(interactive)
(pandoc--save-settings 'project (pandoc--get 'write)))
(defun pandoc-save-global-settings-file ()
"Save the current settings to a global settings file."
(interactive)
(unless (file-directory-p pandoc-data-dir)
(make-directory pandoc-data-dir))
(pandoc--save-settings 'global (pandoc--get 'write)))
(defun pandoc--save-settings (type format &optional no-confirm)
"Save the settings of the current buffer.
TYPE must be a quoted symbol and specifies the type of settings
file. It can be `local', `project', or `global'. FORMAT is the
output format for which the settings are to be saved. If
NO-CONFIRM is non-nil, any existing settings file is overwritten
without asking."
(let* ((filename (buffer-file-name))
(settings pandoc--local-settings)
(settings-file (if (eq type 'global)
(pandoc--create-global-settings-filename format)
(pandoc--create-settings-filename type filename format))))
(if (and (not no-confirm)
(file-exists-p settings-file)
(not (y-or-n-p (format "%s file `%s' already exists. Overwrite? "
(capitalize (symbol-name type))
(file-name-nondirectory settings-file)))))
(message "%s file not written." (capitalize (symbol-name type)))
(with-temp-buffer
(let ((print-length nil)
(print-level nil)
(print-circle nil))
(insert ";; -*- mode: lisp-data -*-\n\n"
(format ";; pandoc-mode %s settings file%s\n"
type
(if (eq type 'local)
(concat " for " (file-name-nondirectory filename))
""))
(format ";; saved on %s\n\n" (format-time-string "%Y.%m.%d %H:%M")))
(pp settings (current-buffer)))
(let ((make-backup-files nil))
(write-region (point-min) (point-max) settings-file))
(message "%s settings file written to `%s'." (capitalize (symbol-name type)) (file-name-nondirectory settings-file)))
(setq pandoc--settings-modified-flag nil))))
(defun pandoc-revert-settings ()
"Revert settings for the current buffer.
The settings file is reread from disk, so that any changes made
to the settings that have not been saved are reverted."
(interactive)
(let ((format (pandoc--get 'write)))
(setq pandoc--local-settings (copy-tree pandoc--options))
(pandoc--load-settings-profile format 'no-confirm)))
(defun pandoc-load-default-settings ()
"Load the default settings of the file in the current buffer.
This function is for use in `pandoc-mode-hook'."
(pandoc--load-settings-profile "default"))
(defun pandoc--load-settings-profile (format &optional no-confirm)
"Load the options for FORMAT from the corresponding settings file.
If NO-CONFIRM is t, no confirmation is asked if the current
settings have not been saved."
(pandoc--load-settings-for-file (when (buffer-file-name)
(expand-file-name (buffer-file-name)))
format
no-confirm))
(defun pandoc--load-settings-for-file (file format &optional no-confirm)
"Load the settings file of FILE for FORMAT.
Search for a local, a project and a global settings file, in that
order, and load the first one that exists and is readable.
If NO-CONFIRM is t, no confirmation is asked if the current
settings have not been saved. FILE must be an absolute path
name. If FILE is nil, a global settings file is read, if any.
The settings are stored in the current buffer's
`pandoc--local-settings'. Return nil if no settings or project
file is found for FILE, otherwise non-nil."
(when (and (not no-confirm)
pandoc--settings-modified-flag
(y-or-n-p (format "Current settings for format \"%s\" modified. Save first? " (pandoc--get 'write))))
(pandoc--save-settings 'local (pandoc--get 'write) t))
(let (settings)
;; first try to read local settings
(when file
(setq settings (cons 'local (pandoc--read-settings-from-file (pandoc--create-settings-filename 'local file format)))))
;; if it fails, try project settings
(when (and file (not (cdr settings)))
(setq settings (cons 'project (pandoc--read-settings-from-file (pandoc--create-settings-filename 'project file format)))))
;; if that fails too, or if there is no file, try reading global settings
(unless (cdr settings)
(setq settings (cons 'global (pandoc--read-settings-from-file (pandoc--create-global-settings-filename format)))))
;; now set them
(when (cdr settings)
(setq pandoc--local-settings (cdr settings))
(message "%s settings file loaded for format \"%s\"." (capitalize (symbol-name (car settings))) format))))
(defun pandoc--read-settings-from-file (file)
"Read the settings in FILE and return them.
If FILE does not exist or cannot be read, return nil."
(if (file-readable-p file)
(with-temp-buffer
(insert-file-contents file)
(goto-char (point-min))
(if (looking-at "#") ; We're probably dealing with an old settings file.
(pandoc--read-old-settings-from-buffer)
(let ((flist (when (search-forward "(" nil t)
(forward-char -1)
(read (current-buffer)))))
(if (listp flist)
flist))))))
(defun pandoc--read-old-settings-from-buffer ()
"Read old-style settings from the current buffer.
`pandoc--settings-modified-flag' is set, so that the user will be
asked to save the settings on exit. Return an alist with the
options and their values."
(goto-char (point-min))
(let (options) ; we collect the options in a list
(while (re-search-forward "^\\([a-z-]*\\)::\\(.*?\\)$" nil t)
(let ((option (intern (match-string 1)))
(value (match-string 2)))
;; If the option is a variable or extension, we read its name and
;; value and add them to the alist as a dotted list.
(push (if (memq option '(variable read-extensions write-extensions))
(progn
(string-match "^\\(.*?\\):\\(.*?\\)$" value)
(cons option (cons (match-string 1 value)
(if (eq option 'variable)
(match-string 2 value)
(intern (match-string 2 value))))))
(cons option (cond
((string-match "^[0-9]$" value) (string-to-number value))
((string= "t" value) t)
((string= "nil" value) nil)
(t value))))
options)))
;; `options' isn't in the proper format for pandoc--local-settings yet:
;; there may be multiple variables and extensions in it. Since we're in
;; a temp buffer, we can simply use pandoc--set to set all options and
;; then return the local value of `pandoc--local-settings'.
(setq pandoc--local-settings (copy-tree pandoc--options))
(mapc (lambda (option)
(pandoc--set (car option) (cdr option)))
options)
pandoc--local-settings))
(defun pandoc-view-output (&optional arg)
"Display the output file.
Try to display the output file generated in the most recent call
to Pandoc. If no output file was produced or Pandoc has not been
called yet, try to open the output file as defined by the current
settings. If the latest Pandoc run produced an error, display an
error message, unless ARG is non-nil, in which case try to open
the output file defined by the current settings. If no output
file exists, display the *Pandoc output* buffer."
(interactive "P")
(when (and (eq pandoc--latest-run 'error) (not arg))
(error "No output file created on most recent call to `pandoc'"))
(let ((format (or (car pandoc--latest-run)
(pandoc--get 'write)))
(file (or (cdr pandoc--latest-run)
(pandoc--compose-output-file-name arg))))
(if file
(if (file-readable-p file)
(let ((handler (if (cl-equalp (file-name-extension file) "pdf")
pandoc-pdf-viewer
(cadr (assoc-string format pandoc-viewers)))))
(cond
((stringp handler)
(start-process "pandoc-viewer" pandoc--viewer-buffer-name handler file))
((eq handler 'emacs)
(let ((buffer (find-file-noselect file)))
(if buffer
(display-buffer buffer)
(error "Could not open %s" file))))
((functionp handler)
(funcall handler file))
(t (error "No viewer defined for output format `%s'" format))))
(error "`%s' is not readable" file))
(pandoc-view-output-buffer))))
(defun pandoc-view-output-buffer ()
"Displays the *Pandoc output* buffer."
(interactive)
(display-buffer pandoc--output-buffer-name))
(defun pandoc-view-settings ()
"Displays the settings file in a *Help* buffer."
(interactive)
;; remove all options that do not have a value.
(let* ((remove-defaults (lambda (alist)
(delq nil (mapcar (lambda (option)
(if (cdr option)
option))
alist))))
(settings (copy-tree pandoc--local-settings))
(read-extensions (assq 'read-extensions settings))
(write-extensions (assq 'write-extensions settings))
(buffers (list (current-buffer)
(if (pandoc--get 'master-file)
(find-file-noselect (pandoc--get 'master-file)))))
(file-locals (pandoc--get-file-local-options buffers)))
(when read-extensions
(setcdr read-extensions (funcall remove-defaults (cdr read-extensions))))
(when write-extensions
(setcdr write-extensions (funcall remove-defaults (cdr write-extensions))))
(setq settings (funcall remove-defaults settings))
(with-help-window " *Pandoc Help*"
(let ((print-length nil)
(print-level nil)
(print-circle nil))
(princ "Current settings:\n\n")
(pp settings)
(when file-locals
(princ "\n\nFile-local settings:\n\n")
(pp file-locals))))))
(defun pandoc-view-log ()
"Display the log buffer in a temporary window."
(interactive)
(display-buffer (get-buffer-create pandoc--log-buffer-name)))
(defun pandoc-insert-@ ()
"Insert a new labeled (@) list marker at point."
(interactive)
(let ((label (pandoc--@-counter-inc)))
(insert (format "(@%s)" label))))
(defun pandoc--collect-@-definitions ()
"Collect (@)-definitions and return them as a list."
(save-excursion
(goto-char (point-min))
(let (definitions)
(while (re-search-forward "^[[:space:]]*\\((@.*?).*\\)$" nil t)
(push (match-string-no-properties 1) definitions))
(nreverse definitions))))
(defun pandoc-select-@ ()
"Show a list of (@)-definitions and allow the user to choose one."
(interactive)
(let ((definitions (pandoc--collect-@-definitions)))
(setq pandoc--window-config (current-window-configuration))
(setq pandoc--pre-select-buffer (current-buffer))
(setq pandoc--@-buffer (get-buffer-create " *Pandoc select*"))
(set-buffer pandoc--@-buffer)
(pandoc-@-mode)
(let ((buffer-read-only nil))
(erase-buffer)
(mapc (lambda (definition)
(insert (concat " " definition "\n\n")))
definitions)
(goto-char (point-min))
(setq pandoc--@-overlay (make-overlay (point-min) (line-end-position)))
(overlay-put pandoc--@-overlay 'face 'highlight))
(select-window (display-buffer pandoc--@-buffer))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Functions to set specific options. ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defun pandoc-set-write (format)
"Set the output format to FORMAT.
If a settings and/or project file exists for FORMAT, they are
loaded. If none exists, all options are unset (except the input
format)."
(interactive (list (completing-read "Set output format to: "
(pandoc--extract-formats 'output)
nil t)))
(when (and pandoc--settings-modified-flag
(y-or-n-p (format "Current settings for output format \"%s\" changed. Save? " (pandoc--get 'write))))
(pandoc--save-settings 'local (pandoc--get 'write) t))
(unless (pandoc--load-settings-profile format t)
(setq pandoc--local-settings (copy-tree pandoc--options))
(pandoc--set 'write format)
(pandoc--set 'read (cdr (assq major-mode pandoc-major-modes))))
(setq pandoc--settings-modified-flag nil)
(setq pandoc--output-format-for-pdf nil)
(message "Output format set to `%s'" format))
(defun pandoc-set-read (format)
"Set the input format to FORMAT."
(interactive (list (completing-read "Set input format to: "
(pandoc--extract-formats 'input)
nil t)))
(pandoc--set 'read format)
(message "Input format set to `%s'" format))
(defun pandoc-set-output (prefix)
"Set the output file.
If called with the PREFIX argument `\\[universal-argument] -' (or
`\\[negative-argument]', the output file is unset. If called
with any other prefix argument, the output file is created on the
basis of the input file and the output format."
(interactive "P")
(pandoc--set 'output
(cond
((eq prefix '-) nil)
((null prefix) (file-name-nondirectory (read-file-name "Output file: ")))
(t t))))
(defun pandoc-set-data-dir (prefix)
"Set the option `Data Directory'.
If called with the PREFIX argument `\\[universal-argument] -' (or
`\\[negative-argument]'), the data directory is set to NIL, which
means use $HOME/.pandoc."
(interactive "P")
(pandoc--set 'data-dir
(if (eq prefix '-)
nil
(read-directory-name "Data directory: " nil nil t))))
(defun pandoc-set-defaults (prefix)
"Set the defaults file.
If called with the PREFIX argument `\\[universal-argument] -' (or
`\\[negative-argument]'), the defaults file is set to nil."
(interactive "P")
(pandoc--set 'defaults (cond
((eq prefix '-) nil)
(t (pandoc--read-file-name "Defaults file: " default-directory (not prefix))))))
(defun pandoc-set-output-dir (prefix)
"Set the option `Output Directory'.
If called with the PREFIX argument `\\[universal-argument] -' (or
`\\[negative-argument]'), the output directory is set to NIL,
which means use the directory of the input file."
(interactive "P")
(pandoc--set 'output-dir
(if (eq prefix '-)
nil
(read-directory-name "Output directory: " nil nil t))))
(defun pandoc-set-extract-media (prefix)
"Set the option `Extract media'.
If called with the PREFIX argument `\\[universal-argument] -' (or
`\\[negative-argument]'), no media files are extracted."
(interactive "P")
(pandoc--set 'extract-media
(if (eq prefix '-)
nil
(read-directory-name "Extract media files to directory: " nil nil t))))
(defun pandoc-set-file-scope (prefix)
"Set the option `File scope'.
If called with the PREFIX argument `\\[universal-argument] -' (or
`\\[negative-argument]'), document scope is used."
(interactive "P")
(pandoc--set 'file-scope (if (eq prefix '-) nil t)))
(defun pandoc-set-master-file (prefix)
"Set the master file.
If called with the PREFIX argument `\\[universal-argument] -' (or
`\\[negative-argument]'), the master file is set to nil, which
means the current file is the master file."
(interactive "P")
(pandoc--set 'master-file (cond
((eq prefix '-) nil)
(t (pandoc--read-file-name "Master file: " default-directory (not prefix))))))
(defun pandoc-set-this-file-as-master ()
"Set the current file as master file.
This option creates a Project settings file in the current
directory to ensure that all files use the current file as master
file."
(interactive)
(pandoc--set 'master-file (buffer-file-name))
(pandoc--save-settings 'project (pandoc--get 'write)))
(defun pandoc-toggle-interactive (prefix)