Change mailing address of FSF, add AUTHORS file.
[muse-el.git] / muse-mode.el
blob494db9c037ce446b209ff664f62998392b4696e7
1 ;;; muse-mode.el --- Mode for editing Muse files; has font-lock support.
3 ;; Copyright (C) 2004, 2005 Free Software Foundation, Inc.
5 ;; This file is not part of GNU Emacs.
7 ;; This is free software; you can redistribute it and/or modify it under
8 ;; the terms of the GNU General Public License as published by the Free
9 ;; Software Foundation; either version 2, or (at your option) any later
10 ;; version.
12 ;; This is distributed in the hope that it will be useful, but WITHOUT
13 ;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 ;; FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15 ;; for more details.
17 ;; You should have received a copy of the GNU General Public License
18 ;; along with GNU Emacs; see the file COPYING. If not, write to the
19 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20 ;; Boston, MA 02110-1301, USA.
22 ;;; Commentary:
24 ;; The Emacs Muse major mode is basically a hyped-up text-mode which
25 ;; knows a lot more about the apparent structure of the document.
27 ;;; Contributors:
29 ;; Andrea Riciputi (ariciputi AT pito DOT com) gave an initial
30 ;; implementation for tag completion by means of the
31 ;; `muse-insert-tag' function.
33 ;;; Code:
35 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
37 ;; Emacs Muse Major Mode
39 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
41 (require 'muse)
42 (require 'muse-regexps)
43 (require 'muse-project)
44 (require 'muse-publish)
46 (autoload 'muse-use-font-lock "muse-colors")
48 (require 'derived)
49 (condition-case nil
50 (require 'pcomplete) ; load if available
51 (error nil))
53 ;;; Options:
55 (defgroup muse-mode nil
56 "Options controlling the behaviour of the Muse editing Mode.
57 See `muse-publish' for more information."
58 :group 'muse)
60 (defcustom muse-mode-highlight-p t
61 "If non-nil, highlight the content of Muse buffers."
62 :type 'boolean
63 :require 'muse-colors
64 :group 'muse-mode)
66 (defcustom muse-mode-auto-p t
67 "If non-nil, automagically determine when Muse mode should be activated."
68 :type 'boolean
69 :set (function
70 (lambda (sym value)
71 (if value
72 (add-hook 'find-file-hooks 'muse-mode-maybe)
73 (remove-hook 'find-file-hooks 'muse-mode-maybe))
74 (set sym value)))
75 :group 'muse-mode)
77 (defcustom muse-mode-hook nil
78 "A hook that is run when Muse mode is entered."
79 :type 'hook
80 :options '(flyspell-mode footnote-mode turn-on-auto-fill
81 highlight-changes-mode)
82 :group 'muse-mode)
84 (defvar muse-mode-map
85 (let ((map (make-sparse-keymap)))
86 (define-key map [(control ?c) (control ?a)] 'muse-index)
87 (define-key map [(control ?c) (control ?b)] 'muse-browse-result)
88 (define-key map [(control ?c) (control ?c)] 'muse-follow-name-at-point)
89 (define-key map [(control ?c) (control ?e)] 'muse-edit-link-at-point)
90 (define-key map [(control ?c) (control ?t)] 'muse-publish-this-file)
91 (define-key map [(control ?c) (control ?v)] 'muse-follow-name-at-point)
93 (define-key map [(control ?c) (control ?l)] 'font-lock-mode)
95 (define-key map [(control ?c) ?=] 'muse-what-changed)
97 (define-key map [tab] 'muse-next-reference)
98 (define-key map [(control ?i)] 'muse-next-reference)
100 (if (featurep 'xemacs)
101 (progn
102 (define-key map [(button2)] 'muse-follow-name-at-mouse)
103 (define-key map [(shift button2)]
104 'muse-follow-name-at-mouse-other-window))
105 (define-key map [(shift control ?m)]
106 'muse-follow-name-at-point-other-window)
107 (define-key map [mouse-2] 'muse-follow-name-at-mouse)
108 (define-key map [(shift mouse-2)]
109 'muse-follow-name-at-mouse-other-window))
111 (if (featurep 'xemacs)
112 (define-key map [(shift tab)] 'muse-previous-reference)
113 (define-key map [(shift iso-lefttab)] 'muse-previous-reference)
114 (define-key map [(shift control ?i)] 'muse-previous-reference))
116 (define-key map [(control ?c) (control ?f)] 'muse-project-find-file)
117 (define-key map [(control ?c) (control ?p)] 'muse-project-publish)
119 (define-key map [(control ?c) tab] 'muse-insert-tag)
120 (define-key map [(control ?c) (control ?i)] 'muse-insert-tag)
122 (when (featurep 'pcomplete)
123 (define-key map [(meta tab)] 'pcomplete)
124 (define-key map [(meta control ?i)] 'pcomplete))
126 map)
127 "Keymap used by Emacs Muse mode.")
129 ;; Code:
131 ;;;###autoload
132 (define-derived-mode muse-mode text-mode "Muse"
133 "Muse is an Emacs mode for authoring and publishing documents.
134 \\{muse-mode-map}"
135 ;; because we're not inheriting from normal-mode, we need to
136 ;; explicitly run file variables if the user wants to
137 (condition-case err
138 (hack-local-variables)
139 (error (message "File local-variables error: %s"
140 (prin1-to-string err))))
141 (if muse-mode-highlight-p
142 (muse-use-font-lock))
143 (setq muse-current-project (muse-project-of-file))
144 (muse-project-set-variables)
145 (when (featurep 'pcomplete)
146 ;; if pcomplete is available, set it up!
147 (set (make-variable-buffer-local 'pcomplete-default-completion-function)
148 'muse-mode-completions)
149 (set (make-variable-buffer-local 'pcomplete-command-completion-function)
150 'muse-mode-completions)
151 (set (make-variable-buffer-local 'pcomplete-parse-arguments-function)
152 'muse-mode-current-word)))
154 (defun muse-mode-maybe ()
155 "Maybe turn Emacs Muse mode on for this file."
156 (let ((project (muse-project-of-file)))
157 (and project
158 (funcall (or (muse-get-keyword :major-mode (cadr project) t)
159 'muse-mode)))))
161 ;;; Support page name completion using pcomplete
163 (defun muse-completions ()
164 "Return a list of possible completions names for this buffer."
165 (let ((project (muse-project-of-file)))
166 (if project
167 (while (pcomplete-here
168 (mapcar 'car (muse-project-file-alist project)))))))
170 (defun muse-current-word ()
171 (let ((end (point)))
172 (save-restriction
173 (save-excursion
174 (skip-chars-backward (concat "^\\["
175 muse-regexp-space))
176 (narrow-to-region (point) end))
177 (pcomplete-parse-buffer-arguments))))
179 ;;; Navigate/visit links or URLs. Use TAB, S-TAB and RET (or mouse-2).
181 (defun muse-link-at-point (&optional pos)
182 "Return link text if a URL or Muse link name is at point."
183 (let ((case-fold-search nil)
184 (here (or pos (point))))
185 (when (or (null pos)
186 (and (char-after pos)
187 (not (eq (char-syntax (char-after pos)) ?\ ))))
188 (save-excursion
189 (goto-char here)
190 (skip-chars-backward (concat "^'\"<>{}("
191 muse-regexp-space))
192 (or (and (looking-at muse-url-regexp)
193 (match-string 0))
194 (and (or (looking-at muse-link-regexp)
195 (and (search-backward "[[" (line-beginning-position) t)
196 (looking-at muse-link-regexp)))
197 (<= here (match-end 0))
198 (match-string 1)))))))
200 (defun muse-make-link (link &optional name)
201 "Return a link to LINK with NAME as the text."
202 (if (and name
203 link
204 (not (string= name ""))
205 (not (string= link name)))
206 (concat "[[" (or link "") "][" name "]]")
207 (concat "[[" (or link "") "]]")))
209 (defun muse-edit-link-at-point ()
210 "Edit the current link.
211 Do not rename the page originally referred to."
212 (interactive)
213 (let (old-name)
214 (if (muse-link-at-point)
215 (replace-match
216 (muse-make-link
217 (read-string "Link: "
218 (match-string-no-properties 1))
219 (read-string "Text: "
220 (match-string-no-properties 2)))
221 t t)
222 (error "There is no valid link at point"))))
224 (defun muse-visit-link (link &optional other-window)
225 "Visit the URL or link named by LINK-NAME."
226 (let ((visit-link-function
227 (muse-get-keyword :visit-link (cadr (muse-project-of-file)) t)))
228 (if visit-link-function
229 (funcall visit-link-function link other-window)
230 (if (string-match muse-url-regexp link)
231 (browse-url link)
232 (let (anchor)
233 (if (string-match "#" link)
234 (setq anchor (substring link (match-beginning 0))
235 link (substring link 0 (match-beginning 0))))
236 (let ((project (muse-project-of-file)))
237 (if project
238 (muse-project-find-file link project
239 (and other-window
240 'find-file-other-window))
241 (if other-window
242 (find-file-other-window link)
243 (find-file link))))
244 (if anchor
245 (search-forward anchor nil t)))))))
247 (defun muse-browse-result (style &optional other-window)
248 "Visit the current page's published result."
249 (interactive (list (muse-publish-get-style) current-prefix-arg))
250 (setq style (muse-style style))
251 (let ((result-path
252 (muse-publish-output-file buffer-file-name
253 (muse-style-element :path style) style)))
254 (if (not (file-readable-p result-path))
255 (error "Cannot open output file '%s" result-path)
256 (if other-window
257 (find-file-other-window result-path)
258 (let ((func (muse-style-element :browser style t)))
259 (if func
260 (funcall func result-path)
261 (message "The publishing style %s does not support browsing."
262 style)))))))
264 (defun muse-follow-name-at-point (&optional other-window)
265 "Visit the link at point, or insert a newline if none."
266 (interactive "P")
267 (let ((link (muse-link-at-point)))
268 (if link
269 (muse-visit-link link other-window)
270 (error "There is no valid link at point"))))
272 (defun muse-follow-name-at-point-other-window ()
273 "Visit the link at point in other window."
274 (interactive)
275 (muse-follow-name-at-point t))
277 (defun muse-follow-name-at-mouse (event &optional other-window)
278 "Visit the link at point, or yank text if none."
279 (interactive "eN")
280 (save-excursion
281 (cond ((fboundp 'event-window) ; XEmacs
282 (set-buffer (window-buffer (event-window event)))
283 (and (funcall (symbol-function 'event-point) event)
284 (goto-char (funcall (symbol-function 'event-point) event))))
285 ((fboundp 'posn-window) ; Emacs
286 (set-buffer (window-buffer (posn-window (event-start event))))
287 (goto-char (posn-point (event-start event)))))
288 (muse-follow-name-at-point other-window)))
290 (defun muse-follow-name-at-mouse-other-window (event)
291 "Visit the link at point"
292 (interactive "e")
293 ;; throw away the old window position, since other-window will
294 ;; change it anyway
295 (select-window (car (cadr event)))
296 (muse-follow-name-at-mouse event t))
298 (defun muse-next-reference ()
299 "Move forward to next Muse link or URL, cycling if necessary."
300 (interactive)
301 (let ((case-fold-search nil)
302 (cycled 0) pos)
303 (save-excursion
304 (if (muse-link-at-point)
305 (goto-char (match-end 0)))
306 (while (< cycled 2)
307 (if (re-search-forward
308 (concat "\\(" muse-link-regexp "\\|"
309 muse-url-regexp "\\)") nil t)
310 (setq pos (match-beginning 0)
311 cycled 2)
312 (goto-char (point-min))
313 (setq cycled (1+ cycled)))))
314 (if pos
315 (goto-char pos))))
317 (defun muse-previous-reference ()
318 "Move backward to the next Muse link or URL, cycling if necessary.
319 This function is not entirely accurate, but it's close enough."
320 (interactive)
321 (let ((case-fold-search nil)
322 (cycled 0) pos)
323 (save-excursion
324 (while (< cycled 2)
325 (if (re-search-backward
326 (concat "\\(" muse-link-regexp "\\|"
327 muse-url-regexp "\\)") nil t)
328 (setq pos (point)
329 cycled 2)
330 (goto-char (point-max))
331 (setq cycled (1+ cycled)))))
332 (if pos
333 (goto-char pos))))
335 (defun muse-what-changed ()
336 "Show the unsaved changes that have been made to the current file."
337 (interactive)
338 (diff-backup buffer-file-name))
340 ;;; Generate an index of all known Muse pages
342 (defun muse-generate-index (&optional as-list exclude-private)
343 "Generate an index of all Muse pages."
344 (let ((files (sort (copy-alist (muse-project-file-alist))
345 (function
346 (lambda (l r)
347 (string-lessp (car l) (car r))))))
348 file)
349 (with-current-buffer (get-buffer-create "*Muse Index*")
350 (erase-buffer)
351 (while files
352 (unless (and exclude-private
353 (muse-project-private-p (cdar files)))
354 (insert (if as-list "- " "") "[[" (caar files) "]]\n"))
355 (setq files (cdr files)))
356 (current-buffer))))
358 (defun muse-index ()
359 "Display an index of all known Muse pages."
360 (interactive)
361 (message "Generating Muse index...")
362 (let ((project (muse-project)))
363 (with-current-buffer (muse-generate-index)
364 (goto-char (point-min))
365 (muse-mode)
366 (setq muse-current-project project)
367 (pop-to-buffer (current-buffer))))
368 (message "Generating Muse index...done"))
370 ;;; Insert tags interactively on C-c TAB
372 (defvar muse-tag-history nil
373 "List of recently-entered tags; used by `muse-insert-tag'.
374 If you want a tag to start as the default, you may manually set
375 this variable to a list.")
377 (defvar muse-custom-tags nil
378 "Keep track of any new tags entered in `muse-insert-tag'.
379 If there are (X)HTML tags that you use frequently with that
380 function, you might want to set this manually.")
382 (defun muse-insert-tag (tag)
383 "Insert a tag interactively with a blank line after it."
384 (interactive
385 (list
386 (completing-read
387 (concat "Tag: "
388 (when muse-tag-history
389 (concat "(default: " (car muse-tag-history) ") ")))
390 (nconc (mapcar 'car muse-publish-markup-tags)
391 muse-custom-tags)
392 nil nil nil 'muse-tag-history
393 (car muse-tag-history))))
394 (when (equal tag "")
395 (setq tag (car muse-tag-history)))
396 (let ((tag-entry (assoc tag muse-publish-markup-tags))
397 (options ""))
398 ;; Add to custom list if no entry exists
399 (unless tag-entry
400 (add-to-list 'muse-custom-tags tag))
401 ;; Get option
402 (when (nth 2 tag-entry)
403 (setq options (read-string "Option: ")))
404 (unless (equal options "")
405 (setq options (concat " " options)))
406 ;; Insert the tag, closing if necessary
407 (when tag (insert (concat "<" tag options ">")))
408 (when (nth 1 tag-entry)
409 (insert (concat "\n\n</" tag ">\n"))
410 (forward-line -2))))
412 (provide 'muse-mode)
414 ;;; muse-mode.el ends here