1
1
;;; muse.el --- an authoring and publishing tool for Emacs
3
;; Copyright (C) 2004, 2005, 2006 Free Software Foundation, Inc.
3
;; Copyright (C) 2004, 2005, 2006, 2007 Free Software Foundation, Inc.
5
5
;; Emacs Lisp Archive Entry
8
;; Date: Tue 10-Oct-2006
8
;; Date: Sun 17-Jun-2007
9
9
;; Keywords: hypermedia
10
10
;; Author: John Wiegley (johnw AT gnu DOT org)
11
;; Maintainer: Michael Olson (mwolson AT gnu DOT org)
11
;; Maintainer: Michael Olson <mwolson@gnu.org>
12
12
;; Description: An authoring and publishing tool for Emacs
13
;; URL: http://www.mwolson.org/projects/EmacsMuse.html
13
;; URL: http://mwolson.org/projects/EmacsMuse.html
14
14
;; Compatibility: Emacs21 XEmacs21 Emacs22
16
16
;; This file is part of Emacs Muse. It is not part of GNU Emacs.
79
96
(defun muse-update-file-extension (sym val)
80
97
"Update the value of `muse-file-extension'."
81
(when (and (boundp sym) (symbol-value sym))
98
(when (and (featurep 'muse-mode)
99
(boundp sym) (stringp (symbol-value sym))
100
(or (not (stringp val))
101
(not (string= (symbol-value sym) val))))
82
102
;; remove old auto-mode-alist association
83
103
(setq auto-mode-alist
84
104
(delete (cons (concat "\\." (symbol-value sym) "\\'")
85
105
'muse-mode-choose-mode)
86
106
auto-mode-alist)))
88
;; associate .muse with muse-mode
108
;; associate the new file extension with muse-mode
109
(when (and (featurep 'muse-mode)
111
(or (not (stringp (symbol-value sym)))
112
(not (string= (symbol-value sym) val))))
90
113
(add-to-list 'auto-mode-alist
91
114
(cons (concat "\\." val "\\'")
92
115
'muse-mode-choose-mode)))
93
(when (fboundp 'muse-update-ignored-extensions-regexp)
116
;; update the ignored extensions regexp
117
(when (and (fboundp 'muse-update-ignored-extensions-regexp)
118
(or (not (stringp (symbol-value sym)))
120
(not (string= (symbol-value sym) val))))
94
121
(muse-update-ignored-extensions-regexp
95
122
'muse-ignored-extensions muse-ignored-extensions)))
97
124
(defcustom muse-file-extension "muse"
98
"File extension of Muse files. Omit the period at the beginning."
125
"File extension of Muse files. Omit the period at the beginning.
126
If you don't want Muse files to have an extension, set this to nil."
100
128
(const :tag "None" nil)
227
280
(with-current-buffer ,temp-buffer
228
281
(set-buffer-modified-p nil))
229
282
(unless debug-on-error (kill-buffer ,temp-buffer)))))))
230
284
(put 'muse-with-temp-buffer 'lisp-indent-function 0)
231
285
(put 'muse-with-temp-buffer 'edebug-form-spec '(body))
287
(defun muse-collect-alist (list element &optional test)
288
"Collect items from LIST whose car is equal to ELEMENT.
289
If TEST is specified, use it to compare ELEMENT."
290
(unless test (setq test 'equal))
293
(when (funcall test element (car item))
294
(setq items (cons item items))))
297
(defmacro muse-sort-with-closure (list predicate closure)
298
"Sort LIST, stably, comparing elements using PREDICATE.
299
Returns the sorted list. LIST is modified by side effects.
300
PREDICATE is called with two elements of list and CLOSURE.
301
PREDICATE should return non-nil if the first element should sort
303
`(sort ,list (lambda (a b) (funcall ,predicate a b ,closure))))
305
(put 'muse-sort-with-closure 'lisp-indent-function 0)
306
(put 'muse-sort-with-closure 'edebug-form-spec '(form function-form form))
308
(defun muse-sort-by-rating (rated-list &optional test)
309
"Sort RATED-LIST according to the rating of each element.
310
The rating is stripped out in the returned list.
311
Default sorting is highest-first.
313
If TEST if specified, use it to sort the list."
314
(unless test (setq test '>))
315
(mapcar (function cdr)
316
(muse-sort-with-closure
318
(lambda (a b closure)
319
(let ((na (numberp (car a)))
320
(nb (numberp (car b))))
321
(cond ((and na nb) (funcall closure (car a) (car b)))
326
(defun muse-escape-specials-in-string (specials string &optional reverse)
327
"Apply the transformations in SPECIALS to STRING.
329
The transforms should form a fully reversible and non-ambiguous
330
syntax when STRING is parsed from left to right.
332
If REVERSE is specified, reverse an already-escaped string."
333
(let ((rules (mapcar (lambda (rule)
334
(cons (regexp-quote (if reverse
337
(if reverse (car rule) (cdr rule))))
341
(goto-char (point-min))
344
(unless (catch 'found
346
(when (looking-at (car rule))
347
(replace-match (cdr rule) t t)
352
(defun muse-trim-whitespace (string)
353
"Return a version of STRING with no initial nor trailing whitespace."
354
(muse-replace-regexp-in-string
355
(concat "\\`[" muse-regexp-blank "]+\\|[" muse-regexp-blank "]+\\'")
358
(defun muse-path-sans-extension (path)
359
"Return PATH sans final \"extension\".
361
The extension, in a file name, is the part that follows the last `.',
362
except that a leading `.', if any, doesn't count.
364
This differs from `file-name-sans-extension' in that it will
365
never modify the directory part of the path."
366
(concat (file-name-directory path)
367
(file-name-nondirectory (file-name-sans-extension path))))
233
369
;; The following code was extracted from cl
235
371
(defun muse-const-expr-p (x)
281
417
;; Compatibility functions
283
(defun muse-looking-back (regexp &optional limit)
284
(if (fboundp 'looking-back)
285
(looking-back regexp limit)
419
(if (fboundp 'looking-back)
420
(defalias 'muse-looking-back 'looking-back)
421
(defun muse-looking-back (regexp &optional limit &rest ignored)
287
423
(re-search-backward (concat "\\(?:" regexp "\\)\\=") limit t))))
289
(defun muse-line-end-position (&optional n)
290
426
(if (fboundp 'line-end-position)
291
(line-end-position n)
292
(save-excursion (end-of-line n) (point))))
427
(defalias 'muse-line-end-position 'line-end-position)
428
(defun muse-line-end-position (&optional n)
429
(save-excursion (end-of-line n) (point))))
294
(defun muse-line-beginning-position (&optional n)
295
431
(if (fboundp 'line-beginning-position)
296
(line-beginning-position n)
297
(save-excursion (beginning-of-line n) (point))))
432
(defalias 'muse-line-beginning-position 'line-beginning-position)
433
(defun muse-line-beginning-position (&optional n)
434
(save-excursion (beginning-of-line n) (point))))
299
(defun muse-match-string-no-properties (num &optional string)
300
436
(if (fboundp 'match-string-no-properties)
301
(match-string-no-properties num string)
302
(match-string num string)))
437
(defalias 'muse-match-string-no-properties 'match-string-no-properties)
438
(defun muse-match-string-no-properties (num &optional string)
439
(match-string num string))))
304
441
(defun muse-replace-regexp-in-string (regexp replacement text &optional fixedcase literal)
305
442
"Replace REGEXP with REPLACEMENT in TEXT.
444
Return a new string containing the replacements.
306
446
If fourth arg FIXEDCASE is non-nil, do not alter case of replacement text.
307
447
If fifth arg LITERAL is non-nil, insert REPLACEMENT literally."
449
((fboundp 'replace-in-string)
450
(replace-in-string text regexp replacement literal))
309
451
((fboundp 'replace-regexp-in-string)
310
452
(replace-regexp-in-string regexp replacement text fixedcase literal))
311
((fboundp 'replace-in-string)
312
(replace-in-string text regexp replacement literal))
313
(t (while (string-match regexp text)
314
(setq text (replace-match replacement fixedcase literal text)))
453
(t (let ((repl-len (length replacement))
455
(unless (string= regexp "")
457
(while (setq start (string-match regexp text start))
458
(setq start (+ start repl-len)
459
text (replace-match replacement fixedcase literal
317
(defun muse-add-to-invisibility-spec (element)
318
"Add ELEMENT to `buffer-invisibility-spec'.
463
(if (fboundp 'add-to-invisibility-spec)
464
(defalias 'muse-add-to-invisibility-spec 'add-to-invisibility-spec)
465
(defun muse-add-to-invisibility-spec (element)
466
"Add ELEMENT to `buffer-invisibility-spec'.
319
467
See documentation for `buffer-invisibility-spec' for the kind of elements
320
468
that can be added."
321
(if (fboundp 'add-to-invisibility-spec)
322
(add-to-invisibility-spec element)
323
469
(if (eq buffer-invisibility-spec t)
324
470
(setq buffer-invisibility-spec (list t)))
325
471
(setq buffer-invisibility-spec
326
472
(cons element buffer-invisibility-spec))))
328
(defun muse-read-directory-name (prompt &optional dir default-dirname mustmatch initial)
329
"Read directory name - see `read-file-name' for details."
330
(if (fboundp 'read-directory-name)
331
(read-directory-name prompt dir default-dirname mustmatch initial)
474
(if (fboundp 'read-directory-name)
475
(defalias 'muse-read-directory-name 'read-directory-name)
476
(defun muse-read-directory-name (prompt &optional dir default-dirname mustmatch initial)
477
"Read directory name - see `read-file-name' for details."
333
479
(setq dir default-directory))
334
480
(read-file-name prompt dir (or default-dirname
363
531
;; Link-handling functions and variables
533
(defun muse-get-link (&optional target)
534
"Based on the match data, retrieve the link.
535
Use TARGET to get the string, if it is specified."
536
(muse-match-string-no-properties 1 target))
538
(defun muse-get-link-desc (&optional target)
539
"Based on the match data, retrieve the link description.
540
Use TARGET to get the string, if it is specified."
541
(muse-match-string-no-properties 2 target))
543
(defvar muse-link-specials
547
"Syntax used for escaping and unescaping links.
548
This allows brackets to occur in explicit links as long as you
549
use the standard Muse functions to create them.")
365
551
(defun muse-link-escape (text)
366
552
"Escape characters in TEXT that conflict with the explicit link
370
(muse-replace-regexp-in-string "\\[" "%5B" text t t)
371
(muse-replace-regexp-in-string "\\]" "%5D" text t t)
555
(muse-escape-specials-in-string muse-link-specials text)))
375
557
(defun muse-link-unescape (text)
376
558
"Un-escape characters in TEXT that conflict with the explicit
380
(muse-replace-regexp-in-string "%5B" "[" text t t)
381
(muse-replace-regexp-in-string "%5D" "]" text t t)
561
(muse-escape-specials-in-string muse-link-specials text t)))
385
563
(defun muse-handle-url (&optional string)
386
564
"If STRING or point has a URL, match and return it."
456
629
(muse-link-unescape
459
(or link (match-string 1))))))
632
(or link (muse-get-link))))))
634
;; Movement functions
636
(defun muse-list-item-type (str)
637
"Determine the type of list given STR.
638
Returns either 'ul, 'ol, 'dl-term, 'dl-entry, or nil."
640
(cond ((or (string= str "")
643
((string-match muse-dl-entry-regexp str)
645
((string-match muse-dl-term-regexp str)
647
((string-match muse-ol-item-regexp str)
649
((string-match muse-ul-item-regexp str)
653
(defun muse-list-item-critical-point (&optional offset)
654
"Figure out where the important markup character for the
655
currently-matched list item is.
657
If OFFSET is specified, it is the number of groupings outside of
658
the contents of `muse-list-item-regexp'."
659
(unless offset (setq offset 0))
660
(if (match-end (+ offset 2))
661
;; at a definition list
662
(match-end (+ offset 2))
663
;; at a different kind of list
664
(match-beginning (+ offset 1))))
666
(defun muse-forward-paragraph (&optional pattern)
667
"Move forward safely by one paragraph, or according to PATTERN."
668
(when (get-text-property (point) 'end-list)
669
(goto-char (next-single-property-change (point) 'end-list)))
670
(setq pattern (if pattern
671
(concat "^\\(?:" pattern "\\|\n\\|\\'\\)")
672
"^\\s-*\\(\n\\|\\'\\)"))
673
(let ((next-list-end (or (next-single-property-change (point) 'end-list)
676
(if (re-search-forward pattern nil t)
677
(goto-char (match-beginning 0))
678
(goto-char (point-max)))
679
(when (> (point) next-list-end)
680
(goto-char next-list-end))))
682
(defun muse-forward-list-item-1 (type empty-line indented-line)
683
"Determine whether a nested list item is after point."
684
(if (match-beginning 1)
685
;; if we are given a dl entry, skip past everything on the same
686
;; level, except for other dl entries
687
(and (eq type 'dl-entry)
688
(not (eq (char-after (match-beginning 2)) ?\:)))
689
;; blank line encountered with no list item on the same
694
(and (looking-at indented-line)
695
(not (looking-at empty-line))))
696
;; found that this blank line is followed by some
697
;; indentation, plus other text, so we'll keep
703
(defun muse-forward-list-item (type indent &optional no-skip-nested)
704
"Move forward to the next item of TYPE.
705
Return non-nil if successful, nil otherwise.
706
The beginning indentation is given by INDENT.
708
If NO-SKIP-NESTED is non-nil, do not skip past nested items.
709
Note that if you desire this behavior, you will also need to
710
provide a very liberal INDENT value, such as
711
\(concat \"[\" muse-regexp-blank \"]*\")."
712
(let* ((list-item (format muse-list-item-regexp indent))
713
(empty-line (concat "^[" muse-regexp-blank "]*\n"))
714
(indented-line (concat "^" indent "[" muse-regexp-blank "]"))
715
(list-pattern (concat "\\(?:" empty-line "\\)?"
716
"\\(" list-item "\\)")))
718
(muse-forward-paragraph list-pattern)
719
;; make sure we don't go past boundary
720
(and (not (or (get-text-property (point) 'end-list)
721
(>= (point) (point-max))))
722
;; move past markup that is part of another construct
723
(or (and (match-beginning 1)
724
(or (get-text-property
725
(muse-list-item-critical-point 1) 'muse-link)
727
(muse-list-item-critical-point 1) 'face)))
729
(and (not no-skip-nested)
730
(muse-forward-list-item-1 type empty-line
732
(cond ((or (get-text-property (point) 'end-list)
733
(>= (point) (point-max)))
734
;; at a list boundary, so stop
736
((let ((str (when (match-beginning 2)
737
;; get the entire line
739
(goto-char (match-beginning 2))
740
(buffer-substring (muse-line-beginning-position)
741
(muse-line-end-position))))))
742
(and str (eq type (muse-list-item-type str))))
743
;; same type, so indicate that there are more items to be
745
(goto-char (match-beginning 1)))
747
(when (match-beginning 1)
748
(goto-char (match-beginning 1)))
749
;; move to just before foreign list item markup
752
(defun muse-goto-tag-end (tag nested)
753
"Move forward past the end of TAG.
755
If NESTED is non-nil, look for other instances of this tag that
756
may be nested inside of this tag, and skip past them."
758
(search-forward (concat "</" tag ">") nil t)
760
(tag-regexp (concat "\\(<\\(/?\\)" tag ">\\)"))
762
(while (and (> nesting 0)
763
(setq match-found (re-search-forward tag-regexp nil t)))
764
(if (string-equal (match-string 2) "/")
765
(setq nesting (1- nesting))
766
(setq nesting (1+ nesting))))
463
769
;;; muse.el ends here