From 57366f385a2f1f35bbe741d7542096db81368c72 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Thu, 17 Mar 2011 11:23:07 +0100 Subject: Big changes, last one before I wipe it all. * Added muse * Added graphviz-dot-mode * Remove all trailing whitespace on save. This is the last commit I'm going to do before throwing it all away again. --- emacs.d/elisp/muse/muse-mode.el | 1013 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 1013 insertions(+) create mode 100644 emacs.d/elisp/muse/muse-mode.el (limited to 'emacs.d/elisp/muse/muse-mode.el') diff --git a/emacs.d/elisp/muse/muse-mode.el b/emacs.d/elisp/muse/muse-mode.el new file mode 100644 index 0000000..9659843 --- /dev/null +++ b/emacs.d/elisp/muse/muse-mode.el @@ -0,0 +1,1013 @@ +;;; muse-mode.el --- mode for editing Muse files; has font-lock support + +;; Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010 +;; Free Software Foundation, Inc. + +;; This file is part of Emacs Muse. It is not part of GNU Emacs. + +;; Emacs Muse 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, or (at your +;; option) any later version. + +;; Emacs Muse 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 Emacs Muse; see the file COPYING. If not, write to the +;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +;; Boston, MA 02110-1301, USA. + +;;; Commentary: + +;; The Emacs Muse major mode is basically a hyped-up text-mode which +;; knows a lot more about the apparent structure of the document. + +;;; Contributors: + +;; Andrea Riciputi (ariciputi AT pito DOT com) gave an initial +;; implementation for tag completion by means of the `muse-insert-tag' +;; function. + +;; Per B. Sederberg (per AT med DOT upenn DOT edu) contributed the +;; insertion of relative links and list items, backlink searching, and +;; other things as well. + +;; Stefan Schlee (stefan_schlee AT yahoo DOT com) fixed a bug in +;; muse-next-reference and muse-previous-reference involving links +;; that begin at point 1. + +;; Gregory Collins (greg AT gregorycollins DOT net) fixed a bug with +;; paragraph separation and headings when filling. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; Emacs Muse Major Mode +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(provide 'muse-mode) + +(require 'muse) +(require 'muse-regexps) +(require 'muse-project) + +(autoload 'muse-use-font-lock "muse-colors") +(autoload 'muse-publish-this-file "muse-publish") +(autoload 'muse-publish-get-style "muse-publish") +(autoload 'muse-publish-output-file "muse-publish") + +(require 'derived) +(eval-when-compile + (condition-case nil + (require 'pcomplete) ; load if available + (error nil))) + +;;; Options: + +(defgroup muse-mode nil + "Options controlling the behavior of the Muse editing Mode." + :group 'muse) + +(defcustom muse-mode-highlight-p t + "If non-nil, highlight the content of Muse buffers." + :type 'boolean + :require 'muse-colors + :group 'muse-mode) + +(defcustom muse-mode-auto-p nil + "If non-nil, automagically determine when Muse mode should be activated." + :type 'boolean + :set (function + (lambda (sym value) + (if value + (add-hook 'find-file-hooks 'muse-mode-maybe) + (remove-hook 'find-file-hooks 'muse-mode-maybe)) + (set sym value))) + :group 'muse-mode) + +(defun muse-mode-maybe-after-init () + (when muse-mode-auto-p + (add-hook 'find-file-hooks 'muse-mode-maybe))) + +;; If the user sets this value in their init file, make sure that +;; it takes effect +(add-hook 'after-init-hook 'muse-mode-maybe-after-init) + +(defcustom muse-mode-intangible-links nil + "If non-nil, use the intangible property on links. +This can cause problems with flyspell (and potentially fill-mode), +so only enable this if you don't use either of these." + :type 'boolean + :group 'muse-mode) + +(defcustom muse-mode-hook nil + "A hook that is run when Muse mode is entered." + :type 'hook + :options '(flyspell-mode footnote-mode turn-on-auto-fill + highlight-changes-mode) + :group 'muse-mode) + +(defcustom muse-grep-command + "find %D -type f ! -name '*~' | xargs -I {} echo \\\"{}\\\" | xargs egrep -n -e \"%W\"" + "The command to use when grepping for backlinks and other +searches through the muse projects. The string %D is replaced by +the directories from muse-project-alist, space-separated. The +string %W is replaced with the name of the muse page or whatever +else you are searching for. This command has been modified to +handle spaces in filenames, which were giving egrep a problem. + +Note: We highly recommend using glimpse to search large projects. +To use glimpse, install and edit a file called .glimpse_exclude +in your home directory. Put a list of glob patterns in that file +to exclude Emacs backup files, etc. Then, run the indexer using: + + glimpseindex -o + +Once that's completed, customize this variable to have the +following value: + + glimpse -nyi \"%W\" + +Your searches will go much, much faster, especially for very +large projects. Don't forget to add a user cronjob to update the +index at intervals." + :type 'string + :group 'muse-mode) + +(defvar muse-insert-map + (let ((map (make-sparse-keymap))) + (define-key map "l" 'muse-insert-relative-link-to-file) + (define-key map "t" 'muse-insert-tag) + (define-key map "u" 'muse-insert-url) + + map)) + +;;; Muse mode + +(defvar muse-mode-map + (let ((map (make-sparse-keymap))) + (define-key map [(control ?c) (control ?a)] 'muse-index) + (define-key map [(control ?c) (control ?e)] 'muse-edit-link-at-point) + (define-key map [(control ?c) (control ?l)] 'font-lock-mode) + (define-key map [(control ?c) (control ?t)] + 'muse-project-publish-this-file) + (define-key map [(control ?c) (control ?T)] 'muse-publish-this-file) + (define-key map [(control ?c) (meta control ?t)] 'muse-publish-this-file) + (define-key map [(control ?c) (control ?v)] 'muse-browse-result) + + (define-key map [(control ?c) ?=] 'muse-what-changed) + + (define-key map [tab] 'muse-next-reference) + (define-key map [(control ?i)] 'muse-next-reference) + + (if (featurep 'xemacs) + (progn + (define-key map [(button2)] 'muse-follow-name-at-mouse) + (define-key map [(shift button2)] + 'muse-follow-name-at-mouse-other-window)) + (define-key map [(shift control ?m)] + 'muse-follow-name-at-point-other-window) + (define-key map [mouse-2] 'muse-follow-name-at-mouse) + (define-key map [(shift mouse-2)] + 'muse-follow-name-at-mouse-other-window)) + + (define-key map [(shift tab)] 'muse-previous-reference) + (unless (featurep 'xemacs) + (define-key map [(shift iso-lefttab)] 'muse-previous-reference) + (define-key map [(shift control ?i)] 'muse-previous-reference)) + + (define-key map [(control ?c) (control ?f)] 'muse-project-find-file) + (define-key map [(control ?c) (control ?p)] 'muse-project-publish) + + (define-key map [(control ?c) (control ?i)] 'muse-insert-thing) + (define-key map [(control ?c) tab] 'muse-insert-thing) + + ;; Searching functions + (define-key map [(control ?c) (control ?b)] 'muse-find-backlinks) + (define-key map [(control ?c) (control ?s)] 'muse-search) + + ;; Enhanced list functions + (define-key map [(meta return)] 'muse-insert-list-item) + (define-key map [(control ?>)] 'muse-increase-list-item-indentation) + (define-key map [(control ?<)] 'muse-decrease-list-item-indentation) + + (when (featurep 'pcomplete) + (define-key map [(meta tab)] 'pcomplete) + (define-key map [(meta control ?i)] 'pcomplete)) + + map) + "Keymap used by Emacs Muse mode.") + +;;;###autoload +(define-derived-mode muse-mode text-mode "Muse" + "Muse is an Emacs mode for authoring and publishing documents. +\\{muse-mode-map}" + ;; Since we're not inheriting from normal-mode, we need to + ;; explicitly run file variables. + (condition-case err + (hack-local-variables) + (error (message "File local-variables error: %s" + (prin1-to-string err)))) + ;; Avoid lock-up caused by use of the 'intangible' text property + ;; with flyspell. + (unless muse-mode-intangible-links + (set (make-local-variable 'inhibit-point-motion-hooks) t)) + (setq muse-current-project (muse-project-of-file)) + (muse-project-set-variables) + ;; Make fill not split up links + (when (boundp 'fill-nobreak-predicate) + (make-local-variable 'fill-nobreak-predicate) + ;; Work around annoying inconsistency in fill handling between + ;; Emacs 21 and 22. + (if (< emacs-major-version 22) + (setq fill-nobreak-predicate 'muse-mode-fill-nobreak-p) + (add-to-list 'fill-nobreak-predicate + 'muse-mode-fill-nobreak-p))) + ;; Make fill work nicely with item lists + (let ((regexp (concat "\\s-+\\(-\\|[0-9]+\\.\\)\\s-+" + "\\|\\[[0-9]+\\]\\s-*" + "\\|.*\\s-*::\\s-+" + "\\|\\*+\\s-+"))) + (set (make-local-variable 'adaptive-fill-regexp) + (concat regexp "\\|\\s-*")) + (set (make-local-variable 'paragraph-start) + (concat paragraph-start "\\|" regexp)) + (set (make-local-variable 'paragraph-separate) + (concat paragraph-separate "\\|\\*+\\s-+"))) + (set (make-local-variable 'fill-paragraph-function) + 'muse-mode-fill-paragraph) + + ;; Comment syntax is `; comment' + (set (make-local-variable 'comment-start) + "; ") + (set (make-local-variable 'comment-start-skip) + "^;\\s-+") + (set (make-local-variable 'indent-line-function) + #'ignore) + ;; If we're using Emacs21, this makes flyspell work like it should + (when (boundp 'flyspell-generic-check-word-p) + (set (make-local-variable 'flyspell-generic-check-word-p) + 'muse-mode-flyspell-p)) + ;; If pcomplete is available, set it up + (when (featurep 'pcomplete) + (set (make-local-variable 'pcomplete-default-completion-function) + 'muse-mode-completions) + (set (make-local-variable 'pcomplete-command-completion-function) + 'muse-mode-completions) + (set (make-local-variable 'pcomplete-parse-arguments-function) + 'muse-mode-current-word)) + ;; Initialize any auto-generated variables + (run-hooks 'muse-update-values-hook) + (when muse-mode-highlight-p + (muse-use-font-lock))) + +(put 'muse-mode + 'flyspell-mode-predicate + 'muse-mode-flyspell-p) + +(defun muse-mode-fill-nobreak-p () + "Return nil if we should allow a fill to occur at point. +Otherwise return non-nil. + +This is used to keep long explicit links from being mangled by +fill mode." + (save-excursion + (save-match-data + (and (re-search-backward "\\[\\[\\|\\]\\]" + (line-beginning-position) t) + (string= (or (match-string 0) "") + "[["))))) + +(defun muse-mode-fill-paragraph (arg) + "If a definition list is at point, use special filling rules for it. +Otherwise return nil to let the normal filling function take care +of things. + +ARG is passed to `fill-paragraph'." + (let ((count 2)) + (and (not (muse-mode-fill-nobreak-p)) + (save-excursion + (beginning-of-line) + (and (looking-at muse-dl-term-regexp) + (prog1 t + ;; Take initial whitespace into account + (when (looking-at (concat "[" muse-regexp-blank "]+")) + (setq count (+ count (length (match-string 0)))))))) + (let ((fill-prefix (make-string count ?\ )) + (fill-paragraph-function nil)) + (prog1 t + (fill-paragraph arg)))))) + +(defun muse-mode-flyspell-p () + "Return non-nil if we should allow spell-checking to occur at point. +Otherwise return nil. + +This is used to keep links from being improperly colorized by flyspell." + (let ((pos (if (bobp) (point) (1- (point))))) + (and (not (get-text-property pos 'muse-no-flyspell)) + (not (get-text-property pos 'muse-link)) + (save-match-data + (null (muse-link-at-point)))))) + +;;;###autoload +(defun muse-mode-choose-mode () + "Turn the proper Emacs Muse related mode on for this file." + (let ((project (muse-project-of-file))) + (funcall (or (and project (muse-get-keyword :major-mode (cadr project) t)) + 'muse-mode)))) + +(defun muse-mode-maybe () + "Maybe turn Emacs Muse mode on for this file." + (let ((project (muse-project-of-file))) + (and project + (funcall (or (muse-get-keyword :major-mode (cadr project) t) + 'muse-mode))))) + +;;; Enhanced list editing + +(defun muse-on-blank-line () + "See if point is on a blank line" + (save-excursion + (beginning-of-line) + (looking-at (concat "[" muse-regexp-blank "]*$")))) + +(defun muse-get-paragraph-start () + "Return the start of the current paragraph. This function will +return nil if there are no prior paragraphs and the beginning of +the line if point is on a blank line." + (let ((para-start (concat "^[" muse-regexp-blank "]*$"))) + ;; search back to start of paragraph + (save-excursion + (save-match-data + (if (not (muse-on-blank-line)) + (re-search-backward para-start nil t) + (line-beginning-position)))))) + +(defun muse-insert-thing () + "Prompt for something to insert into the current buffer." + (interactive) + (message "Insert:\nl link\nt Muse tag\nu URL") + (let (key cmd) + (let ((overriding-local-map muse-insert-map)) + (setq key (read-key-sequence nil))) + (if (commandp (setq cmd (lookup-key muse-insert-map key))) + (progn (message "") + (call-interactively cmd)) + (message "Not inserting anything")))) + +;;;###autoload +(defun muse-insert-list-item () + "Insert a list item at the current point, taking into account +your current list type and indentation level." + (interactive) + (let ((newitem " - ") + (itemno nil) + (pstart (muse-get-paragraph-start)) + (list-item (format muse-list-item-regexp + (concat "[" muse-regexp-blank "]*")))) + ;; search backwards for start of current item + (save-excursion + (when (re-search-backward list-item pstart t) + ;; save the matching item + (setq newitem (match-string 0)) + ;; see what type it is + (if (string-match "::" (match-string 0)) + ;; is a definition, replace the term + (setq newitem (concat " " + (read-string "Term: ") + " :: ")) + ;; see if it's a numbered list + (when (string-match "[0-9]+" newitem) + ;; is numbered, so increment + (setq itemno (1+ + (string-to-number + (match-string 0 newitem)))) + (setq newitem (replace-match + (number-to-string itemno) + nil nil newitem)))))) + ;; insert the new item + (insert (concat "\n" newitem)))) + +(defun muse-alter-list-item-indentation (operation) + "Alter the indentation of the current list item. +Valid values of OPERATION are 'increase and 'decrease." + (let ((pstart (muse-get-paragraph-start)) + (list-item (format muse-list-item-regexp + (concat "[" muse-regexp-blank "]*"))) + beg move-func indent) + ;; search backwards until start of paragraph to see if we are on a + ;; current item + (save-excursion + (if (or (progn (goto-char (muse-line-beginning-position)) + ;; we are on an item + (looking-at list-item)) + ;; not on item, so search backwards + (re-search-backward list-item pstart t)) + (let ((beg (point))) + ;; we are on an item + (setq indent (buffer-substring (match-beginning 0) + (match-beginning 1))) + (muse-forward-list-item (muse-list-item-type (match-string 1)) + (concat "[" muse-regexp-blank "]*") + t) + (save-restriction + (narrow-to-region beg (point)) + (goto-char (point-min)) + (let ((halt nil)) + (while (< (point) (point-max)) + ;; increase or decrease the indentation + (unless halt + (cond ((eq operation 'increase) + (insert " ")) + ((eq operation 'decrease) + (if (looking-at " ") + ;; we have enough space, so delete it + (delete-region (match-beginning 0) + (match-end 0)) + (setq halt t))))) + (forward-line 1))))) + ;; we are not on an item, so warn + (message "You are not on a list item."))))) + +;;;###autoload +(defun muse-increase-list-item-indentation () + "Increase the indentation of the current list item." + (interactive) + (muse-alter-list-item-indentation 'increase)) + +;;;###autoload +(defun muse-decrease-list-item-indentation () + "Decrease the indentation of the current list item." + (interactive) + (muse-alter-list-item-indentation 'decrease)) + +;;; Support page name completion using pcomplete + +(defun muse-mode-completions () + "Return a list of possible completions names for this buffer." + (let ((project (muse-project-of-file))) + (if project + (while (pcomplete-here + (mapcar 'car (muse-project-file-alist project))))))) + +(defun muse-mode-current-word () + (let ((end (point))) + (save-excursion + (save-restriction + (skip-chars-backward (concat "^\\[\n" muse-regexp-blank)) + (narrow-to-region (point) end)) + (pcomplete-parse-buffer-arguments)))) + +;;; Navigate/visit links or URLs. Use TAB, S-TAB and RET (or mouse-2). + +(defun muse-link-at-point (&optional pos) + "Return link text if a URL or link is at point." + (let ((case-fold-search nil) + (inhibit-point-motion-hooks t) + (here (or pos (point)))) + ;; if we are using muse-colors, we can just use link properties to + ;; determine whether we are on a link + (if (featurep 'muse-colors) + (when (get-text-property here 'muse-link) + (save-excursion + (when (and (not (bobp)) + (get-text-property (1- here) 'muse-link)) + (goto-char (or (previous-single-property-change here 'muse-link) + (point-min)))) + (if (looking-at muse-explicit-link-regexp) + (progn + (goto-char (match-beginning 1)) + (muse-handle-explicit-link)) + (muse-handle-implicit-link)))) + ;; use fallback method to find a link + (when (or (null pos) + (and (char-after pos) + (not (eq (char-syntax (char-after pos)) ?\ )))) + (save-excursion + (goto-char here) + ;; check for explicit link here or before point + (if (or (looking-at muse-explicit-link-regexp) + (and + (re-search-backward "\\[\\[\\|\\]\\]" + (muse-line-beginning-position) + t) + (string= (or (match-string 0) "") "[[") + (looking-at muse-explicit-link-regexp))) + (progn + (goto-char (match-beginning 1)) + (muse-handle-explicit-link)) + (goto-char here) + ;; check for bare URL or other link type + (skip-chars-backward (concat "^'\"<>{}(\n" muse-regexp-blank)) + (and (looking-at muse-implicit-link-regexp) + (muse-handle-implicit-link)))))))) + +(defun muse-make-link (link &optional desc) + "Return a link to LINK with DESC as the description." + (when (string-match muse-explicit-link-regexp link) + (unless desc (setq desc (muse-get-link-desc link))) + (setq link (muse-get-link link))) + (if (and desc + link + (not (string= desc "")) + (not (string= link desc))) + (concat "[[" (muse-link-escape link) "][" (muse-link-escape desc) "]]") + (concat "[[" (or (muse-link-escape link) "") "]]"))) + +;;;###autoload +(defun muse-insert-relative-link-to-file () + "Insert a relative link to a file, with optional description, at point." + ;; Perhaps the relative location should be configurable, so that the + ;; file search would start in the publishing directory and then + ;; insert the link relative to the publishing directory + (interactive) + (insert + (muse-make-link (file-relative-name (read-file-name "Link: ")) + (read-string "Text: ")))) + +(defcustom muse-insert-url-initial-input "http://" + "The string to insert before reading a URL interactively. +This is used by the `muse-insert-url' command." + :type 'string + :group 'muse-mode) + +(defun muse-insert-url () + "Insert a URL, with optional description, at point." + (interactive) + (insert + (muse-make-link (read-string "URL: " muse-insert-url-initial-input) + (read-string "Text: ")))) + +;;;###autoload +(defun muse-edit-link-at-point () + "Edit the current link. +Do not rename the page originally referred to." + (interactive) + (if (muse-link-at-point) + (let ((link (muse-link-unescape (muse-get-link))) + (desc (muse-link-unescape (muse-get-link-desc)))) + (replace-match + (save-match-data + (muse-make-link + (read-string "Link: " link) + (read-string "Text: " desc))) + t t)) + (error "There is no valid link at point"))) + +(defun muse-visit-link-default (link &optional other-window) + "Visit the URL or link named by LINK. +If ANCHOR is specified, search for it after opening LINK. + +This is the default function to call when visiting links; it is +used by `muse-visit-link' if you have not specified :visit-link +in `muse-project-alist'." + (if (string-match muse-url-regexp link) + (muse-browse-url link) + (let (anchor + base-buffer) + (when (string-match "#" link) + (setq anchor (substring link (match-beginning 0)) + link (if (= (match-beginning 0) 0) + ;; If there is an anchor but no link, default + ;; to the current page. + nil + (substring link 0 (match-beginning 0))))) + (when link + (setq base-buffer (get-buffer link)) + (if (and base-buffer (not (buffer-file-name base-buffer))) + ;; If file is temporary (no associated file), just switch to + ;; the buffer + (if other-window + (switch-to-buffer-other-window base-buffer) + (switch-to-buffer base-buffer)) + (let ((project (muse-project-of-file))) + (if project + (muse-project-find-file link project + (and other-window + 'find-file-other-window)) + (if other-window + (find-file-other-window link) + (find-file link)))))) + (when anchor + (let ((pos (point)) + (regexp (concat "^\\W*" (regexp-quote anchor) "\\b")) + last) + (goto-char (point-min)) + (while (and (setq last (re-search-forward regexp nil t)) + (muse-link-at-point))) + (unless last + (goto-char pos) + (message "Could not find anchor `%s'" anchor))))))) + +(defun muse-visit-link (link &optional other-window) + "Visit the URL or link named by LINK." + (let ((visit-link-function + (muse-get-keyword :visit-link (cadr (muse-project-of-file)) t))) + (if visit-link-function + (funcall visit-link-function link other-window) + (muse-visit-link-default link other-window)))) + +;;;###autoload +(defun muse-browse-result (style &optional other-window) + "Visit the current page's published result." + (interactive + (list (muse-project-get-applicable-style buffer-file-name + (cddr muse-current-project)) + current-prefix-arg)) + (setq style (muse-style style)) + (muse-project-publish-this-file nil style) + (let* ((output-dir (muse-style-element :path style)) + (output-suffix (muse-style-element :osuffix style)) + (output-path (muse-publish-output-file buffer-file-name output-dir + style)) + (target (if output-suffix + (concat (muse-path-sans-extension output-path) + output-suffix) + output-path)) + (muse-current-output-style (list :base (car style) + :path output-dir))) + (if (not (file-readable-p target)) + (error "Cannot open output file '%s'" target) + (if other-window + (find-file-other-window target) + (let ((func (muse-style-element :browser style t))) + (if func + (funcall func target) + (message "The %s publishing style does not support browsing." + style))))))) + +;;;###autoload +(defun muse-follow-name-at-point (&optional other-window) + "Visit the link at point." + (interactive "P") + (let ((link (muse-link-at-point))) + (if link + (muse-visit-link link other-window) + (error "There is no valid link at point")))) + +;;;###autoload +(defun muse-follow-name-at-point-other-window () + "Visit the link at point in other window." + (interactive) + (muse-follow-name-at-point t)) + +(defun muse-follow-name-at-mouse (event &optional other-window) + "Visit the link at point, or yank text if none is found." + (interactive "eN") + (unless + (save-excursion + (cond ((fboundp 'event-window) ; XEmacs + (set-buffer (window-buffer (event-window event))) + (and (funcall (symbol-function 'event-point) event) + (goto-char (funcall (symbol-function 'event-point) + event)))) + ((fboundp 'posn-window) ; Emacs + (set-buffer (window-buffer (posn-window (event-start event)))) + (goto-char (posn-point (event-start event))))) + (let ((link (muse-link-at-point))) + (when link + (muse-visit-link link other-window) + t))) + ;; Fall back to normal binding for this event + (call-interactively + (lookup-key (current-global-map) (this-command-keys))))) + +(defun muse-follow-name-at-mouse-other-window (event) + "Visit the link at point" + (interactive "e") + ;; throw away the old window position, since other-window will + ;; change it anyway + (select-window (car (cadr event))) + (muse-follow-name-at-mouse event t)) + +;;;###autoload +(defun muse-next-reference () + "Move forward to next Muse link or URL, cycling if necessary." + (interactive) + (let ((pos)) + (save-excursion + (when (get-text-property (point) 'muse-link) + (goto-char (or (next-single-property-change (point) 'muse-link) + (point-max)))) + + (setq pos (next-single-property-change (point) 'muse-link)) + + (when (not pos) + (if (get-text-property (point-min) 'muse-link) + (setq pos (point-min)) + (setq pos (next-single-property-change (point-min) 'muse-link))))) + + (when pos + (goto-char pos)))) + +;;;###autoload +(defun muse-previous-reference () + "Move backward to the next Muse link or URL, cycling if necessary. +In case of Emacs x <= 21 and ignoring of intangible properties (see +`muse-mode-intangible-links'). + +This function is not entirely accurate, but it's close enough." + (interactive) + (let ((pos)) + (save-excursion + + ;; Hack: The user perceives the two cases of point ("|") + ;; position (1) "|[[" and (2) "[[|" or "][|" as "point is at + ;; start of link". But in the sense of the function + ;; "previous-single-property-change" these two cases are + ;; different. The following code aligns these two cases. Emacs + ;; 21: If the intangible property is ignored case (2) is more + ;; complicate and this hack only solves the problem partially. + ;; + (when (and (get-text-property (point) 'muse-link) + (muse-looking-back "\\[\\|\\]")) + (goto-char (or (previous-single-property-change (point) 'muse-link) + (point-min)))) + + (when (eq (point) (point-min)) + (goto-char (point-max))) + + (setq pos (previous-single-property-change (point) 'muse-link)) + + (when (not pos) + (if (get-text-property (point-min) 'muse-link) + (setq pos (point-min)) + (setq pos (previous-single-property-change (point-max) + 'muse-link))))) + + (when pos + (if (get-text-property pos 'muse-link) + (goto-char pos) + (goto-char (or (previous-single-property-change pos 'muse-link) + (point-min))))))) + +;;;###autoload +(defun muse-what-changed () + "Show the unsaved changes that have been made to the current file." + (interactive) + (diff-backup buffer-file-name)) + + +;;; Find text in project pages, or pages referring to the current page + +(defvar muse-search-history nil) + +(defun muse-grep (string &optional grep-command-no-shadow) + "Grep for STRING in the project directories. +GREP-COMMAND if passed will supplant `muse-grep-command'." + ;; careful - grep-command leaks into compile, so we call it + ;; -no-shadow instead + (require 'compile) + (let* ((str (or grep-command-no-shadow muse-grep-command)) + (muse-directories (mapcar + (lambda (thing) + (car (cadr thing))) + muse-project-alist)) + (dirs (mapconcat (lambda (dir) + (shell-quote-argument + (expand-file-name dir))) + muse-directories " "))) + (if (string= dirs "") + (muse-display-warning + "No directories were found in the current project; aborting search") + (while (string-match "%W" str) + (setq str (replace-match string t t str))) + (while (string-match "%D" str) + (setq str (replace-match dirs t t str))) + (if (fboundp 'compilation-start) + (compilation-start str nil (lambda (&rest args) "*search*") + grep-regexp-alist) + (and (fboundp 'compile-internal) + (compile-internal str "No more search hits" "search" + nil grep-regexp-alist)))))) + +;;;###autoload +(defun muse-search-with-command (text) + "Search for the given TEXT string in the project directories +using the specified command." + (interactive + (list (let ((str (concat muse-grep-command)) pos) + (when (string-match "%W" str) + (setq pos (match-beginning 0)) + (unless (featurep 'xemacs) + (setq pos (1+ pos))) + (setq str (replace-match "" t t str))) + (read-from-minibuffer "Search command: " + (cons str pos) nil nil + 'muse-search-history)))) + (muse-grep nil text)) + +;;;###autoload +(defun muse-search () + "Search for the given TEXT using the default grep command." + (interactive) + (muse-grep (read-string "Search: "))) + +;;;###autoload +(defun muse-find-backlinks () + "Grep for the current pagename in all the project directories." + (interactive) + (muse-grep (muse-page-name))) + + +;;; Generate an index of all known Muse pages + +(defun muse-generate-index (&optional as-list exclude-private) + "Generate an index of all Muse pages." + (let ((index (muse-index-as-string as-list exclude-private))) + (with-current-buffer (get-buffer-create "*Muse Index*") + (erase-buffer) + (insert index) + (current-buffer)))) + +;;;###autoload +(defun muse-index () + "Display an index of all known Muse pages." + (interactive) + (message "Generating Muse index...") + (let ((project (muse-project))) + (with-current-buffer (muse-generate-index) + (goto-char (point-min)) + (muse-mode) + (setq muse-current-project project) + (pop-to-buffer (current-buffer)))) + (message "Generating Muse index...done")) + +(defun muse-index-as-string (&optional as-list exclude-private exclude-current) + "Generate an index of all Muse pages. +If AS-LIST is non-nil, insert a dash and spaces before each item. +If EXCLUDE-PRIVATE is non-nil, exclude files that have private permissions. +If EXCLUDE-CURRENT is non-nil, exclude the current file from the output." + (let ((files (sort (copy-alist (muse-project-file-alist)) + (function + (lambda (l r) + (string-lessp (car l) (car r))))))) + (when (and exclude-current (muse-page-name)) + (setq files (delete (assoc (muse-page-name) files) files))) + (with-temp-buffer + (while files + (unless (and exclude-private + (muse-project-private-p (cdar files))) + (insert (if as-list " - " "") "[[" (caar files) "]]\n")) + (setq files (cdr files))) + (buffer-string)))) + +;;; Insert tags interactively on C-c TAB t + +(defvar muse-tag-history nil + "List of recently-entered tags; used by `muse-insert-tag'. +If you want a tag to start as the default, you may manually set +this variable to a list.") + +(defvar muse-custom-tags nil + "Keep track of any new tags entered in `muse-insert-tag'. +If there are (X)HTML tags that you use frequently with that +function, you might want to set this manually.") + +;;;###autoload +(defun muse-insert-tag (tag) + "Insert a tag interactively with a blank line after it." + (interactive + (list + (funcall + muse-completing-read-function + (concat "Tag: " + (when muse-tag-history + (concat "(default: " (car muse-tag-history) ") "))) + (progn + (require 'muse-publish) + (mapcar 'list (nconc (mapcar 'car muse-publish-markup-tags) + muse-custom-tags))) + nil nil nil 'muse-tag-history + (car muse-tag-history)))) + (when (equal tag "") + (setq tag (car muse-tag-history))) + (unless (interactive-p) + (require 'muse-publish)) + (let ((tag-entry (assoc tag muse-publish-markup-tags)) + (options "")) + ;; Add to custom list if no entry exists + (unless tag-entry + (add-to-list 'muse-custom-tags tag)) + ;; Get option + (when (nth 2 tag-entry) + (setq options (read-string "Option: "))) + (unless (equal options "") + (setq options (concat " " options))) + ;; Insert the tag, closing if necessary + (when tag (insert (concat "<" tag options ">"))) + (when (nth 1 tag-entry) + (insert (concat "\n\n\n")) + (forward-line -2)))) + +;;; Muse list edit minor mode + +(defvar muse-list-edit-minor-mode-map + (let ((map (make-sparse-keymap))) + (define-key map [(meta return)] 'muse-l-e-m-m-insert-list-item) + (define-key map [(control ?>)] 'muse-l-e-m-m-increase-list-item-indent) + (define-key map [(control ?<)] 'muse-l-e-m-m-decrease-list-item-indent) + + map) + "Keymap used by Muse list edit minor mode.") + +(defvar muse-l-e-m-m-list-item-regexp + (concat "^%s\\(\\([^\n" muse-regexp-blank "].*?\\)?::" + "\\(?:[" muse-regexp-blank "]+\\|$\\)" + "\\|[" muse-regexp-blank "]?[-*+][" muse-regexp-blank "]*" + "\\|[" muse-regexp-blank "][0-9]+\\.[" muse-regexp-blank "]*\\)") + "Regexp used to match the beginning of a list item. +This is used by `muse-list-edit-minor-mode'. +The '%s' will be replaced with a whitespace regexp when publishing.") + +(defun muse-l-e-m-m-insert-list-item () + "Insert a list item at the current point, taking into account +your current list type and indentation level." + (interactive) + (let ((muse-list-item-regexp muse-l-e-m-m-list-item-regexp)) + (call-interactively 'muse-insert-list-item))) + +(defun muse-l-e-m-m-increase-list-item-indent () + "Increase the indentation of the current list item." + (interactive) + (let ((muse-list-item-regexp muse-l-e-m-m-list-item-regexp)) + (call-interactively 'muse-increase-list-item-indentation))) + +(defun muse-l-e-m-m-decrease-list-item-indent () + "Decrease the indentation of the current list item." + (interactive) + (let ((muse-list-item-regexp muse-l-e-m-m-list-item-regexp)) + (call-interactively 'muse-decrease-list-item-indentation))) + +(defvar muse-l-e-m-m-data nil + "A list of data that was changed by Muse list edit minor mode.") +(make-variable-buffer-local 'muse-l-e-m-m-data) + +;;;###autoload +(define-minor-mode muse-list-edit-minor-mode + "This is a global minor mode for editing files with lists. +It is meant to be used with other major modes, and not with Muse mode. + +Interactively, with no prefix argument, toggle the mode. +With universal prefix ARG turn mode on. +With zero or negative ARG turn mode off. + +This minor mode provides the Muse keybindings for editing lists, +and support for filling lists properly. + +It recognizes not only Muse-style lists, which use the \"-\" +character or numbers, but also lists that use asterisks or plus +signs. This should make the minor mode generally useful. + +Definition lists and footnotes are also recognized. + +Note that list items may omit leading spaces, for compatibility +with modes that set `left-margin', such as +`debian-changelog-mode'. + +\\{muse-list-edit-minor-mode-map}" + :init-value nil + :lighter "" + :keymap muse-list-edit-minor-mode-map + :global nil + :group 'muse-mode + (if (not muse-list-edit-minor-mode) + ;; deactivate + (when muse-l-e-m-m-data + (setq adaptive-fill-regexp (cdr (assoc "a-f-r" muse-l-e-m-m-data)) + paragraph-start (cdr (assoc "p-s" muse-l-e-m-m-data)) + fill-prefix (cdr (assoc "f-p" muse-l-e-m-m-data))) + (setq muse-l-e-m-m-data nil)) + ;; activate + (unless muse-l-e-m-m-data + ;; save previous fill-related data so we can restore it later + (setq muse-l-e-m-m-data + (list (cons "a-f-r" adaptive-fill-regexp) + (cons "p-s" paragraph-start) + (cons "f-p" fill-prefix)))) + ;; make fill work nicely with item lists + (let ((regexp (concat "\\s-*\\([-*+]\\|[0-9]+\\.\\)\\s-+" + "\\|\\[[0-9]+\\]\\s-*" + "\\|.*\\s-*::\\s-+"))) + (set (make-local-variable 'adaptive-fill-regexp) + (concat regexp "\\|\\s-*")) + (set (make-local-variable 'paragraph-start) + (concat paragraph-start "\\|" regexp))) + ;; force fill-prefix to be nil, because if it is a string that has + ;; initial spaces, it messes up fill-paragraph's algorithm + (set (make-local-variable 'fill-prefix) nil))) + +(defun turn-on-muse-list-edit-minor-mode () + "Unconditionally turn on Muse list edit minor mode." + (muse-list-edit-minor-mode 1)) + +(defun turn-off-muse-list-edit-minor-mode () + "Unconditionally turn off Muse list edit minor mode." + (muse-list-edit-minor-mode -1)) + +;;; muse-mode.el ends here -- cgit v1.2.3-54-g00ecf