(vc-find-file-hook, vc-file-not-found-hook): Use add-hook to install.
[bpt/emacs.git] / lisp / vc-hooks.el
index f7de918..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.6 1992/10/24 20:07:08 rms Exp rms $        
+;; 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.")
 
@@ -100,51 +106,143 @@ the make-backup-files variable.  Otherwise, prevents backups being made.")
           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 is 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.
-  (vc-file-setprop buffer-file-name 'vc-backend nil)
+  (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 ()
@@ -155,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))