Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Octocat-spinner-32-eaf2f5

Cannot retrieve contributors at this time

file 1014 lines (884 sloc) 38.697 kb
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 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013
;;; muse-mode.el --- mode for editing Muse files; has font-lock support

;; Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009
;; 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 <list of Wiki directories>

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</" tag ">\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
Something went wrong with that request. Please try again.