Standardize source headers, appease elint, don't require planner
[muse-el.git] / muse-mode.el
blob8a208e92a59c9fb0f3707c8930c9f42ca71eb70e
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., 59 Temple Place - Suite 330, Boston,
20 ;; MA 02111-1307, 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 ;;; Code:
31 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
33 ;; Emacs Muse Major Mode
35 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
37 (require 'muse)
38 (require 'muse-regexps)
39 (require 'muse-project)
41 (autoload 'muse-use-font-lock "muse-colors")
43 (require 'derived)
44 (condition-case nil
45 (require 'pcomplete) ; load if available
46 (error nil))
48 ;;; Options:
50 (defgroup muse-mode nil
51 "Options controlling the behaviour of the Muse editing Mode.
52 See `muse-publish' for more information."
53 :group 'muse)
55 (defcustom muse-mode-highlight-p t
56 "If non-nil, highlight the content of Muse buffers."
57 :type 'boolean
58 :require 'muse-colors
59 :group 'muse-mode)
61 (defcustom muse-mode-auto-p t
62 "If non-nil, automagically determine when Muse mode should be activated."
63 :type 'boolean
64 :set (function
65 (lambda (sym value)
66 (if value
67 (add-hook 'find-file-hooks 'muse-mode-maybe)
68 (remove-hook 'find-file-hooks 'muse-mode-maybe))
69 (set sym value)))
70 :group 'muse-mode)
72 (defcustom muse-mode-hook nil
73 "A hook that is run when Muse mode is entered."
74 :type 'hook
75 :options '(flyspell-mode footnote-mode turn-on-auto-fill
76 highlight-changes-mode)
77 :group 'muse-mode)
79 (defvar muse-mode-map
80 (let ((map (make-sparse-keymap)))
81 (define-key map [(control ?c) (control ?a)] 'muse-index)
82 (define-key map [(control ?c) (control ?b)] 'muse-browse-result)
83 (define-key map [(control ?c) (control ?c)] 'muse-follow-name-at-point)
84 (define-key map [(control ?c) (control ?e)] 'muse-edit-link-at-point)
85 (define-key map [(control ?c) (control ?t)] 'muse-publish-this-file)
86 (define-key map [(control ?c) (control ?v)] 'muse-follow-name-at-point)
88 (define-key map [(control ?c) (control ?l)] 'font-lock-mode)
90 (define-key map [(control ?c) ?=] 'muse-what-changed)
92 (define-key map [tab] 'muse-next-reference)
93 (define-key map [(control ?i)] 'muse-next-reference)
95 (if (featurep 'xemacs)
96 (progn
97 (define-key map [(button2)] 'muse-follow-name-at-mouse)
98 (define-key map [(shift button2)]
99 'muse-follow-name-at-mouse-other-window))
100 (define-key map [(shift control ?m)]
101 'muse-follow-name-at-point-other-window)
102 (define-key map [mouse-2] 'muse-follow-name-at-mouse)
103 (define-key map [(shift mouse-2)]
104 'muse-follow-name-at-mouse-other-window))
106 (if (featurep 'xemacs)
107 (define-key map [(shift tab)] 'muse-previous-reference)
108 (define-key map [(shift iso-lefttab)] 'muse-previous-reference)
109 (define-key map [(shift control ?i)] 'muse-previous-reference))
111 (define-key map [(control ?c) (control ?f)] 'muse-project-find-file)
112 (define-key map [(control ?c) (control ?p)] 'muse-project-publish)
114 (when (featurep 'pcomplete)
115 (define-key map [(meta tab)] 'pcomplete)
116 (define-key map [(meta control ?i)] 'pcomplete))
118 map)
119 "Keymap used by Emacs Muse mode.")
121 ;; Code:
123 ;;;###autoload
124 (define-derived-mode muse-mode text-mode "Muse"
125 "Muse is an Emacs mode for authoring and publishing documents.
126 \\{muse-mode-map}"
127 ;; because we're not inheriting from normal-mode, we need to
128 ;; explicitly run file variables if the user wants to
129 (condition-case err
130 (hack-local-variables)
131 (error (message "File local-variables error: %s"
132 (prin1-to-string err))))
133 (if muse-mode-highlight-p
134 (muse-use-font-lock))
135 (setq muse-current-project (muse-project-of-file))
136 (muse-project-set-variables)
137 (when (featurep 'pcomplete)
138 ;; if pcomplete is available, set it up!
139 (set (make-variable-buffer-local 'pcomplete-default-completion-function)
140 'muse-mode-completions)
141 (set (make-variable-buffer-local 'pcomplete-command-completion-function)
142 'muse-mode-completions)
143 (set (make-variable-buffer-local 'pcomplete-parse-arguments-function)
144 'muse-mode-current-word)))
146 (defun muse-mode-maybe ()
147 "Maybe turn Emacs Muse mode on for this file."
148 (let ((project (muse-project-of-file)))
149 (and project
150 (funcall (or (muse-get-keyword :major-mode (cadr project) t)
151 'muse-mode)))))
153 ;;; Support page name completion using pcomplete
155 (defun muse-completions ()
156 "Return a list of possible completions names for this buffer."
157 (let ((project (muse-project-of-file)))
158 (if project
159 (while (pcomplete-here
160 (mapcar 'car (muse-project-file-alist project)))))))
162 (defun muse-current-word ()
163 (let ((end (point)))
164 (save-restriction
165 (save-excursion
166 (skip-chars-backward (concat "^\\["
167 muse-regexp-space))
168 (narrow-to-region (point) end))
169 (pcomplete-parse-buffer-arguments))))
171 ;;; Navigate/visit links or URLs. Use TAB, S-TAB and RET (or mouse-2).
173 (defun muse-link-at-point (&optional pos)
174 "Return link text if a URL or Muse link name is at point."
175 (let ((case-fold-search nil)
176 (here (or pos (point))))
177 (when (or (null pos)
178 (and (char-after pos)
179 (not (eq (char-syntax (char-after pos)) ?\ ))))
180 (save-excursion
181 (goto-char here)
182 (skip-chars-backward (concat "^'\"<>{}("
183 muse-regexp-space))
184 (or (and (looking-at muse-url-regexp)
185 (match-string 0))
186 (and (or (looking-at muse-link-regexp)
187 (and (search-backward "[[" (line-beginning-position) t)
188 (looking-at muse-link-regexp)))
189 (<= here (match-end 0))
190 (match-string 1)))))))
192 (defun muse-make-link (link &optional name)
193 "Return a link to LINK with NAME as the text."
194 (if (and name
195 link
196 (not (string= name ""))
197 (not (string= link name)))
198 (concat "[[" (or link "") "][" name "]]")
199 (concat "[[" (or link "") "]]")))
201 (defun muse-edit-link-at-point ()
202 "Edit the current link.
203 Do not rename the page originally referred to."
204 (interactive)
205 (let (old-name)
206 (if (muse-link-at-point)
207 (replace-match
208 (muse-make-link
209 (read-string "Link: "
210 (match-string-no-properties 1))
211 (read-string "Text: "
212 (match-string-no-properties 2)))
213 t t)
214 (error "There is no valid link at point"))))
216 (defun muse-visit-link (link &optional other-window)
217 "Visit the URL or link named by LINK-NAME."
218 (let ((visit-link-function
219 (muse-get-keyword :visit-link (cadr (muse-project-of-file)) t)))
220 (if visit-link-function
221 (funcall visit-link-function link other-window)
222 (if (string-match muse-url-regexp link)
223 (browse-url link)
224 (let (anchor)
225 (if (string-match "#" link)
226 (setq anchor (substring link (match-beginning 0))
227 link (substring link 0 (match-beginning 0))))
228 (let ((project (muse-project-of-file)))
229 (if project
230 (muse-project-find-file link project
231 (and other-window
232 'find-file-other-window))
233 (if other-window
234 (find-file-other-window link)
235 (find-file link))))
236 (if anchor
237 (search-forward anchor nil t)))))))
239 (defun muse-browse-result (style &optional other-window)
240 "Visit the current page's published result."
241 (interactive (list (muse-publish-get-style) current-prefix-arg))
242 (setq style (muse-style style))
243 (let ((result-path
244 (muse-publish-output-file buffer-file-name
245 (muse-style-element :path style) style)))
246 (if (not (file-readable-p result-path))
247 (error "Cannot open output file '%s" result-path)
248 (if other-window
249 (find-file-other-window result-path)
250 (let ((func (muse-style-element :browser style t)))
251 (if func
252 (funcall func result-path)
253 (message "The publishing style %s does not support browsing."
254 style)))))))
256 (defun muse-follow-name-at-point (&optional other-window)
257 "Visit the link at point, or insert a newline if none."
258 (interactive "P")
259 (let ((link (muse-link-at-point)))
260 (if link
261 (muse-visit-link link other-window)
262 (error "There is no valid link at point"))))
264 (defun muse-follow-name-at-point-other-window ()
265 "Visit the link at point in other window."
266 (interactive)
267 (muse-follow-name-at-point t))
269 (defun muse-follow-name-at-mouse (event &optional other-window)
270 "Visit the link at point, or yank text if none."
271 (interactive "eN")
272 (save-excursion
273 (cond ((fboundp 'event-window) ; XEmacs
274 (set-buffer (window-buffer (event-window event)))
275 (and (funcall (symbol-function 'event-point) event)
276 (goto-char (funcall (symbol-function 'event-point) event))))
277 ((fboundp 'posn-window) ; Emacs
278 (set-buffer (window-buffer (posn-window (event-start event))))
279 (goto-char (posn-point (event-start event)))))
280 (muse-follow-name-at-point other-window)))
282 (defun muse-follow-name-at-mouse-other-window (event)
283 "Visit the link at point"
284 (interactive "e")
285 ;; throw away the old window position, since other-window will
286 ;; change it anyway
287 (select-window (car (cadr event)))
288 (muse-follow-name-at-mouse event t))
290 (defun muse-next-reference ()
291 "Move forward to next Muse link or URL, cycling if necessary."
292 (interactive)
293 (let ((case-fold-search nil)
294 (cycled 0) pos)
295 (save-excursion
296 (if (muse-link-at-point)
297 (goto-char (match-end 0)))
298 (while (< cycled 2)
299 (if (re-search-forward
300 (concat "\\(" muse-link-regexp "\\|"
301 muse-url-regexp "\\)") nil t)
302 (setq pos (match-beginning 0)
303 cycled 2)
304 (goto-char (point-min))
305 (setq cycled (1+ cycled)))))
306 (if pos
307 (goto-char pos))))
309 (defun muse-previous-reference ()
310 "Move backward to the next Muse link or URL, cycling if necessary.
311 This function is not entirely accurate, but it's close enough."
312 (interactive)
313 (let ((case-fold-search nil)
314 (cycled 0) pos)
315 (save-excursion
316 (while (< cycled 2)
317 (if (re-search-backward
318 (concat "\\(" muse-link-regexp "\\|"
319 muse-url-regexp "\\)") nil t)
320 (setq pos (point)
321 cycled 2)
322 (goto-char (point-max))
323 (setq cycled (1+ cycled)))))
324 (if pos
325 (goto-char pos))))
327 (defun muse-what-changed ()
328 "Show the unsaved changes that have been made to the current file."
329 (interactive)
330 (diff-backup buffer-file-name))
332 ;;; Generate an index of all known Muse pages
334 (defun muse-generate-index (&optional as-list exclude-private)
335 "Generate an index of all Muse pages."
336 (let ((files (sort (copy-alist (muse-project-file-alist))
337 (function
338 (lambda (l r)
339 (string-lessp (car l) (car r))))))
340 file)
341 (with-current-buffer (get-buffer-create "*Muse Index*")
342 (erase-buffer)
343 (while files
344 (unless (and exclude-private
345 (muse-project-private-p (cdar files)))
346 (insert (if as-list "- " "") "[[" (caar files) "]]\n"))
347 (setq files (cdr files)))
348 (current-buffer))))
350 (defun muse-index ()
351 "Display an index of all known Muse pages."
352 (interactive)
353 (message "Generating Muse index...")
354 (let ((project (muse-project)))
355 (with-current-buffer (muse-generate-index)
356 (goto-char (point-min))
357 (muse-mode)
358 (setq muse-current-project project)
359 (pop-to-buffer (current-buffer))))
360 (message "Generating Muse index...done"))
362 (provide 'muse-mode)
364 ;;; muse-mode.el ends here