(vc-find-file-hook, vc-file-not-found-hook): Use add-hook to install.
[bpt/emacs.git] / lisp / vc-hooks.el
index 5f77991..b258a40 100644 (file)
@@ -1,11 +1,9 @@
-;;; vc-hooks.el -- resident support for version-control
+;;; vc-hooks.el --- resident support for version-control
 
-;; Copyright (C) 1992 Free Software Foundation, Inc.
+;; Copyright (C) 1992, 1993 Free Software Foundation, Inc.
 
 ;; Author: Eric S. Raymond <esr@snark.thyrsus.com>
-;; Version: 4.0
-
-;;     $Id: vc-hooks.el,v 1.44 1992/07/31 06:43:05 esr Exp $   
+;; Version: 5.3
 
 ;; This file is part of GNU Emacs.
 
@@ -40,17 +38,25 @@ when creating new masters.")
   "*If non-nil, backups of registered files are made according to
 the make-backup-files variable.  Otherwise, prevents backups being made.")
 
+(defvar vc-rcs-status t
+  "*If non-nil, revision and locks on RCS working file displayed in modeline.
+Otherwise, not displayed.")
+
 ;; Tell Emacs about this new kind of minor mode
-(if (not (assoc 'vc-mode-string minor-mode-alist))
-    (setq minor-mode-alist (cons '(vc-mode-string vc-mode-string)
+(if (not (assoc 'vc-mode minor-mode-alist))
+    (setq minor-mode-alist (cons '(vc-mode vc-mode)
                                 minor-mode-alist)))
 
-(make-variable-buffer-local 'vc-mode-string)
+(make-variable-buffer-local 'vc-mode)
+(put 'vc-mode 'permanent-local t)
 
 ;; We need a notion of per-file properties because the version
 ;; control state of a file is expensive to derive --- we don't
 ;; want to recompute it even on every find.
 
+(defmacro vc-error-occurred (&rest body)
+  (list 'condition-case nil (cons 'progn (append body '(nil))) '(error t)))
+
 (defvar vc-file-prop-obarray [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
   "Obarray for per-file properties.")
 
@@ -65,71 +71,178 @@ the make-backup-files variable.  Otherwise, prevents backups being made.")
 ;;; actual version-control code starts here
 
 (defun vc-registered (file)
-  ;; Search for a master corresponding to the given file
-  (let ((dirname (or (file-name-directory file) ""))
-       (basename (file-name-nondirectory file)))
-    (catch 'found
-      (mapcar
-       (function (lambda (s)
-         (let ((trial (format (car s) dirname basename)))
-           (if (and (file-exists-p trial)
-                    ;; Make sure the file we found with name
-                    ;; TRIAL is not the source file itself.
-                    ;; That can happen with RCS-style names
-                    ;; if the file name is truncated
-                    ;; (e.g. to 14 chars).  See if either
-                    ;; directory or attributes differ.
-                    (or (not (string= dirname
-                                      (file-name-directory trial)))
-                        (not (equal
-                              (file-attributes file)
-                              (file-attributes trial)))))
-               (throw 'found (cons trial (cdr s)))))))
-       vc-master-templates)
-      nil)
-    ))
+  (let (handler handlers)
+    (if (boundp 'file-name-handler-alist)
+       (save-match-data
+         (setq handlers file-name-handler-alist)
+         (while (and (consp handlers) (null handler))
+           (if (and (consp (car handlers))
+                    (stringp (car (car handlers)))
+                    (string-match (car (car handlers)) file))
+               (setq handler (cdr (car handlers))))
+           (setq handlers (cdr handlers)))))
+    (if handler
+       (funcall handler 'vc-registered file)
+      ;; Search for a master corresponding to the given file
+      (let ((dirname (or (file-name-directory file) ""))
+           (basename (file-name-nondirectory file)))
+       (catch 'found
+         (mapcar
+          (function (lambda (s)
+             (let ((trial (format (car s) dirname basename)))
+               (if (and (file-exists-p trial)
+                        ;; Make sure the file we found with name
+                        ;; TRIAL is not the source file itself.
+                        ;; That can happen with RCS-style names
+                        ;; if the file name is truncated
+                        ;; (e.g. to 14 chars).  See if either
+                        ;; directory or attributes differ.
+                        (or (not (string= dirname
+                                          (file-name-directory trial)))
+                            (not (equal
+                                  (file-attributes file)
+                                  (file-attributes trial)))))
+                   (throw 'found (cons trial (cdr s)))))))
+          vc-master-templates)
+         nil)))))
+
+(defun vc-name (file)
+  "Return the master name of a file, nil if it is not registered."
+  (or (vc-file-getprop file 'vc-name)
+      (let ((name-and-type (vc-registered file)))
+       (if name-and-type
+           (progn
+             (vc-file-setprop file 'vc-backend (cdr name-and-type))
+             (vc-file-setprop file 'vc-name (car name-and-type)))))))
 
 (defun vc-backend-deduce (file)
-  "Return the version-control type of a file, nil if it is not registered"
+  "Return the version-control type of a file, nil if it is not registered."
   (and file
        (or (vc-file-getprop file 'vc-backend)
-          (vc-file-setprop file 'vc-backend (cdr (vc-registered file))))))
+          (let ((name-and-type (vc-registered file)))
+            (if name-and-type
+                (progn
+                  (vc-file-setprop file 'vc-name (car name-and-type))
+                  (vc-file-setprop file 'vc-backend (cdr name-and-type))))))))
 
 (defun vc-toggle-read-only ()
-  "If the file in the current buffer id under version control, perform the
-logical next version-control action; otherwise, just toggle the buffer's
-read-only flag."
+  "Change read-only status of current buffer, perhaps via version control.
+If the buffer is visiting a file registered with version control,
+then check the file in or out.  Otherwise, just change the read-only flag
+of the buffer."
   (interactive)
   (if (vc-backend-deduce (buffer-file-name))
       (vc-next-action nil)
     (toggle-read-only)))
+(define-key global-map "\C-x\C-q" 'vc-toggle-read-only)
 
 (defun vc-mode-line (file &optional label)
-  "Set `vc-mode-string' to display type of version control for FILE.
+  "Set `vc-mode' to display type of version control for FILE.
 The value is set in the current buffer, which should be the buffer
 visiting FILE."
+  (interactive (list buffer-file-name nil))
   (let ((vc-type (vc-backend-deduce file)))
-    (if vc-type
-       (progn
-         (if (null (current-local-map))
-             (use-local-map (make-sparse-keymap)))
-         (define-key (current-local-map) "\C-x\C-q" 'vc-toggle-read-only)
-         (setq vc-mode-string
-               (concat " " (or label (symbol-name vc-type))))))
+    (setq vc-mode
+         (and vc-type
+              (concat " " (or label (symbol-name vc-type))
+                      (if (and vc-rcs-status (eq vc-type 'RCS))
+                          (vc-rcs-status file)))))
     ;; force update of mode line
     (set-buffer-modified-p (buffer-modified-p))
     vc-type))
 
+(defun vc-rcs-status (file)
+  ;; Return string for placement in modeline by `vc-mode-line'.
+  ;; If FILE is not registered under RCS, return nil.
+  ;; If FILE is registered but not locked, return " REV" if there is a head
+  ;; revision and " @@" otherwise.
+  ;; If FILE is locked then return all locks in a string of the
+  ;; form " LOCKER1:REV1 LOCKER2:REV2 ...", where "LOCKERi:" is empty if you
+  ;; are the locker, and otherwise is the name of the locker followed by ":".
+
+  ;; Algorithm: 
+
+  ;; 1. Check for master file corresponding to FILE being visited.
+  ;; 
+  ;; 2. Insert the first few characters of the master file into a work
+  ;; buffer.
+  ;;  
+  ;; 3. Search work buffer for "locks...;" phrase; if not found, then
+  ;; keep inserting more characters until the phrase is found.
+  ;; 
+  ;; 4. Extract the locks, and remove control characters
+  ;; separating them, like newlines; the string " user1:revision1
+  ;; user2:revision2 ..." is returned.
+
+  ;; Limitations:
+
+  ;; The output doesn't show which version you are actually looking at.
+  ;; The modeline can get quite cluttered when there are multiple locks.
+  ;; The head revision is probably not what you want if you've used `rcs -b'.
+
+  (let ((master (vc-name file))
+       found)
+
+    ;; If master file exists, then parse its contents, otherwise we return the 
+    ;; nil value of this if form.
+    (if master
+        (save-excursion
+
+          ;; Create work buffer.
+          (set-buffer (get-buffer-create " *vc-rcs-status*"))
+          (setq buffer-read-only nil
+                default-directory (file-name-directory master))
+          (erase-buffer)
+
+          ;; Check if we have enough of the header.
+         ;; If not, then keep including more.
+          (while
+             (not (or found
+                      (let ((s (buffer-size)))
+                        (goto-char (1+ s))
+                        (zerop (car (cdr (insert-file-contents
+                                          master nil s (+ s 8192))))))))
+           (beginning-of-line)
+           (setq found (re-search-forward "^locks\\([^;]*\\);" nil t)))
+
+          (if found
+             ;; Clean control characters and self-locks from text.
+             (let* ((lock-pattern
+                     (concat "[ \b\t\n\v\f\r]+\\("
+                             (regexp-quote (user-login-name))
+                             ":\\)?"))
+                    (locks
+                     (save-restriction
+                       (narrow-to-region (match-beginning 1) (match-end 1))
+                       (goto-char (point-min))
+                       (while (re-search-forward lock-pattern nil t)
+                         (replace-match (if (eobp) "" "-") t t))
+                       (buffer-string)))
+                    (status
+                     (if (not (string-equal locks ""))
+                         locks
+                       (goto-char (point-min))
+                       (if (looking-at "head[ \b\t\n\v\f\r]+\\([.0-9]+\\)")
+                           (concat "-" (buffer-substring (match-beginning 1)
+                                                         (match-end 1)))
+                         " @@"))))
+               ;; Clean work buffer.
+               (erase-buffer)
+               (set-buffer-modified-p nil)
+               status))))))
+
 ;;; install a call to the above as a find-file hook
 (defun vc-find-file-hook ()
+  ;; Recompute whether file is version controlled,
+  ;; if user has killed the buffer and revisited.
+  (if buffer-file-name
+      (vc-file-setprop buffer-file-name 'vc-backend nil))
   (if (and (vc-mode-line buffer-file-name) (not vc-make-backup-files))
       (progn
        (make-local-variable 'make-backup-files)
        (setq make-backup-files nil))))
 
-(or (memq 'vc-find-file-hook find-file-hooks)
-    (setq find-file-hooks
-         (cons 'vc-find-file-hook find-file-hooks)))
+(add-hook 'find-file-hooks 'vc-find-file-hook)
 
 ;;; more hooks, this time for file-not-found
 (defun vc-file-not-found-hook ()
@@ -140,13 +253,11 @@ Returns t if checkout was successful, nil otherwise."
        (require 'vc)
        (not (vc-error-occurred (vc-checkout buffer-file-name))))))
 
-(or (memq 'vc-file-not-found-hook find-file-not-found-hooks)
-    (setq find-file-not-found-hooks
-         (cons 'vc-file-not-found-hook find-file-not-found-hooks)))
+(add-hook 'find-file-not-found-hooks 'vc-file-not-found-hook)
 
 ;;; Now arrange for bindings and autoloading of the main package.
-;;; Bindings for this have to go in the global map, as it may have
-;;; to coexist with a lot of different major modes.
+;;; Bindings for this have to go in the global map, as we'll often
+;;; want to call them from random buffers.
 
 (setq vc-prefix-map (lookup-key global-map "\C-xv"))
 (if (not (keymapp vc-prefix-map))
@@ -155,7 +266,7 @@ Returns t if checkout was successful, nil otherwise."
       (define-key global-map "\C-xv" vc-prefix-map)
       (define-key vc-prefix-map "a" 'vc-update-change-log)
       (define-key vc-prefix-map "c" 'vc-cancel-version)
-      (define-key vc-prefix-map "d" 'vc-diff)
+      (define-key vc-prefix-map "d" 'vc-directory)
       (define-key vc-prefix-map "h" 'vc-insert-headers)
       (define-key vc-prefix-map "i" 'vc-register)
       (define-key vc-prefix-map "l" 'vc-print-log)
@@ -163,21 +274,9 @@ Returns t if checkout was successful, nil otherwise."
       (define-key vc-prefix-map "s" 'vc-create-snapshot)
       (define-key vc-prefix-map "u" 'vc-revert-buffer)
       (define-key vc-prefix-map "v" 'vc-next-action)
-      (define-key vc-prefix-map "=" 'vc-directory)
+      (define-key vc-prefix-map "=" 'vc-diff)
       ))
 
-(autoload 'vc-update-change-log "vc.el" nil t)
-(autoload 'vc-cancel-version "vc.el" nil t)
-(autoload 'vc-diff "vc.el" nil t)
-(autoload 'vc-insert-headers "vc.el" nil t)
-(autoload 'vc-register "vc.el" nil t)
-(autoload 'vc-print-log "vc.el" nil t)
-(autoload 'vc-retrieve-snapshot "vc.el" nil t)
-(autoload 'vc-creat-snapshot "vc.el" nil t)
-(autoload 'vc-directory "vc.el" nil t)
-(autoload 'vc-revert-buffer "vc.el" nil t)
-(autoload 'vc-next-action "vc.el" nil t)
-
 (provide 'vc-hooks)
 
 ;;; vc-hooks.el ends here