; $Id: rcsview.el,v 1.16 2002/01/24 11:46:01 furuta Exp $ ;; Copyright (c) 2002 Atsushi Furuta. ;; All rights reserved. ;; ;; Redistribution and use in source and binary forms, with or without ;; modification, are permitted provided that the following conditions ;; are met: ;; 1. Redistributions of source code must retain the above copyright ;; notice, this list of conditions and the following disclaimer. ;; 2. Redistributions in binary form must reproduce the above copyright ;; notice, this list of conditions and the following disclaimer in the ;; documentation and/or other materials provided with the distribution. ;; ;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ;; ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ;; IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR ;; PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS ;; BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ;; CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ;; SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR ;; BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, ;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE ;; OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN ;; IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; customize variables ;;; ;;; please set a corrent path name of a installed back-end ;;; executable file "rcsview" ;(defvar *rcsview-backend-command* "/usr/local/bin/rcsview") (defvar *rcsview-backend-command* "~/work/rcs/rcsview") ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; misc library ;;; (defun basename (file) (let ((pos (string-match "/[^/]*$" file))) (if pos (substring file (1+ pos)) file))) (defun dirname (file) (let ((pos (string-match "/[^/]*$" file))) (if pos (substring file 0 pos) "."))) (defun split (sep str) (let ((index 0) (substr) (list nil)) (while (string-match sep str index) (setq substr (substring str index (match-beginning 0))) (setq list (cons substr list)) (setq index (match-end 0))) (setq substr (substring str index)) (setq list (cons substr list)) (nreverse list))) (defun obarray-to-list (obarray) (let ((list nil)) (mapatoms #'(lambda (s) (setq list (cons s list))) obarray))) (defun buffer-match-string (nth) (buffer-substring (match-beginning nth) (match-end nth))) (defun buffer-match-number (nth) (string-to-number (buffer-match-string nth))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; revision number abstraction ;;; (defun rcsview-revision-vector (str) (apply #'vector (mapcar #'string-to-number (split "\\." str)))) (defun rcsview-revision-string (vec) (mapconcat #'number-to-string vec ".")) (defun rcsview-revision-trunk-p (r) (= (length r) 2)) (defun rcsview-revision-ancestor-p (p r) (cond ((> (length p) (length r)) nil) ((rcsview-revision-trunk-p p) (cond ((< (aref p 0) (aref r 0)) t) ((= (aref p 0) (aref r 0)) (<= (aref p 1) (aref r 1))) (t nil))) (t (let ((i 0) (flag t)) (while (and flag (< i (1- (length p)))) (if (not (= (aref p i) (aref r i))) (setq flag nil)) (setq i (1+ i))) (if flag (<= (aref p i) (aref r i)) nil))))) (defun rcsview-revision-same-p (p r) (if (= (length p) (length r)) (let ((i 0) (flag t)) (while (and flag (< i (length p))) (if (not (= (aref p i) (aref r i))) (setq flag nil)) (setq i (1+ i))) flag) nil)) (defun rcsview-revision-trunk-list () (sort (apply #'nconc (mapcar #'(lambda (rev-sym) (if (rcsview-revision-trunk-p (get rev-sym 'rev)) (cons rev-sym nil) nil)) (rcsview-prop-get rcs-file 'rev-list))) #'(lambda (a b) (cond ((eq a b) nil) (t (rcsview-revision-ancestor-p (get a 'rev) (get b 'rev))))))) (defun rcsview-revision-analyze () (let* ((head (rcsview-prop-get rcs-file 'head-revision)) (trunk (rcsview-revision-trunk-list)) (p) (n) (l trunk)) (setq p (car l)) (rcsview-prop-set rcs-file 'root p) (while (cdr l) (setq l (cdr l)) (setq n (car l)) (put p 'next n) (put n 'prev p) (setq p n)) (rcsview-prop-set rcs-file 'curr head))) (defun rcsview-revision-update () (interactive) (let* ((curr (rcsview-prop-get rcs-file 'curr))) (rcsview-log-show curr) (rcsview-text-live-setup curr))) (defun rcsview-revision-next () (interactive) (let* ((curr (rcsview-prop-get rcs-file 'curr)) (next (get curr 'next))) (if next (rcsview-prop-set rcs-file 'curr next))) (rcsview-revision-update)) (defun rcsview-revision-prev () (interactive) (let* ((curr (rcsview-prop-get rcs-file 'curr)) (prev (get curr 'prev))) (if prev (rcsview-prop-set rcs-file 'curr prev))) (rcsview-revision-update)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; obarray ;;; (defun rcsview-obarray-init (rcs-file) (let ((tag-obarray (make-vector 511 0)) (rev-obarray (make-vector 511 0))) (rcsview-prop-set rcs-file 'tag-obarray tag-obarray) (rcsview-prop-set rcs-file 'rev-obarray rev-obarray))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; text buffer ;;; (defface rcsview-face-new '((((class color) (background dark)) (:background "blue")) (t (:background "yellow"))) "Used for displaying the region.") (defvar rcsview-text-mode-map nil) (if (not rcsview-text-mode-map) (progn (setq rcsview-text-mode-map (copy-keymap global-map)) (define-key rcsview-text-mode-map "a" 'rcsview-text-annotate-toggle) (define-key rcsview-text-mode-map "n" 'rcsview-text-next-revision) (define-key rcsview-text-mode-map "p" 'rcsview-text-previous-revision))) (defvar rcsview-text-mode-hook nil) (defun rcsview-text-mode () (setq major-mode 'rcsview-text-mode) (setq mode-name "Rcsview-Text") (use-local-map rcsview-text-mode-map) (make-local-variable 'rcs-file) (run-hooks 'rcsview-text-mode-hook)) (defun rcsview-text-live-p (lifetime rev) (cond ((rcsview-revision-ancestor-p (car lifetime) rev) (let ((l (cdr lifetime)) (f t)) (while (and f l) (if (rcsview-revision-ancestor-p (car l) rev) (setq f nil)) (setq l (cdr l))) f)) (t nil))) (defun rcsview-text-mark-live (ol livep) (cond (livep (overlay-put ol 'invisible nil) (overlay-put ol 'intangible nil)) (t (overlay-put ol 'invisible t) (overlay-put ol 'intangible t)))) (defun rcsview-text-live-setup (rev-sym) (let ((o) (l) (e) (rev (get rev-sym 'rev)) (list (rcsview-prop-get rcs-file 'overlay-list))) (while list (setq e (car list)) (setq l (car e)) (setq o (cdr e)) (rcsview-text-mark-live o (rcsview-text-live-p l rev)) (if (rcsview-revision-same-p (car l) rev) (overlay-put o 'face 'rcsview-face-new) (overlay-put o 'face 'default)) (setq list (cdr list))))) (defun rcsview-text-annotate-string (rev) (let* ((rev-obarray (rcsview-prop-get rcs-file 'rev-obarray)) (rev-sym (intern (rcsview-revision-string rev) rev-obarray)) (time (decode-time (get rev-sym 'date)))) (format "%-12s (%-8s %02d-%3s-%02d): " (symbol-name rev-sym) (get rev-sym 'author) (nth 3 time) (nth (nth 4 time) '("" "Jan" "Feb" "Mar" "Apr" "May" "Jun" "Jul" "Aug" "Sep" "Oct" "Nov" "Dec")) (nth 5 time)))) (defun rcsview-text-annotate-setup (on) (save-excursion (set-buffer (rcsview-prop-get rcs-file 'text-buffer)) (setq buffer-read-only nil) (let ((o) (l) (e) (intangible) (list (rcsview-prop-get rcs-file 'overlay-list))) (while list (setq e (car list)) (setq l (car e)) (setq o (cdr e)) (setq intangible (overlay-get o 'intangible)) (overlay-put o 'intangible nil) (goto-char (overlay-start o)) (while (< (point) (overlay-end o)) (cond (on (insert (rcsview-text-annotate-string (car l)))) (t (looking-at "[0-9.]+ +([a-zA-Z0-9_-]+): ") (delete-region (match-beginning 0) (match-end 0)))) (forward-line)) (overlay-put o 'intangible intangible) (setq list (cdr list)))) (setq buffer-read-only t))) (defun rcsview-text-annotate-toggle () (interactive) (let ((on (not (rcsview-prop-get rcs-file 'annotate-flag)))) (rcsview-text-annotate-setup on) (rcsview-prop-set rcs-file 'annotate-flag on))) (defun rcsview-text-buffer-init (file) (let* ((t-buffer (generate-new-buffer (format "RCS-Text %s" (basename file)))) (r-buffer (rcsview-prop-get file 'rlog-buffer)) (lines) (lifetime) (lstring) (start) (overlay) (list nil)) (save-excursion (set-buffer t-buffer) (setq buffer-read-only nil) (erase-buffer) (call-process *rcsview-backend-command* nil t-buffer nil "-u" file) (goto-char (point-min)) (while (looking-at "^\\([0-9]+\\) \\([0-9\.\,]+\\)\n") (setq lines (buffer-match-number 1)) (setq lstring (buffer-match-string 2)) (goto-char (match-end 0)) (delete-region (match-beginning 0) (match-end 0)) (setq lifetime (mapcar #'rcsview-revision-vector (split "," lstring))) (setq start (point)) (forward-line lines) (setq overlay (make-overlay start (point))) (setq list (cons (cons lifetime overlay) list))) (set-buffer-modified-p nil) (goto-char (point-min)) (setq buffer-read-only t) (rcsview-text-mode) (setq rcs-file file) (rcsview-prop-set rcs-file 'overlay-list (nreverse list)) (cd (dirname file)) (rcsview-text-live-setup (rcsview-prop-get file 'head-revision))) (rcsview-prop-set file 'annotate-flag nil) t-buffer)) (defun rcsview-text-next-revision () (interactive) (rcsview-revision-next)) (defun rcsview-text-previous-revision () (interactive) (rcsview-revision-prev)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; rlog buffer ;;; (defun rcsview-revision-join (tag-alist rev-alist) (let ((alist rev-alist) (rev nil) (rev2 nil) (rev-assoc nil) (tag-assoc nil)) (while (consp tag-alist) (setq tag-assoc (car tag-alist)) (setq tag-alist (cdr tag-alist)) (setq rev (cdr tag-assoc)) ;; convert revision x.y.0.z (if (string-match "\\.0\\.\\([0-9]+\\)$" rev) (progn ;; to x.y (setq rev2 (substring rev 0 (match-beginning 0))) ;; or to x.y.z.1 (setq rev (concat "." (substring rev (match-beginning 1) (match-end 1)) ".1")))) (setq rev-assoc (assoc rev rev-alist)) (if (null rev-assoc) (setq rev-assoc (assoc rev2 rev-alist))) (if rev-assoc (setq alist (cons (cons (car tag-assoc) (cdr rev-assoc)) alist)))) alist)) ;(defun rcsview-rlog-goto-revision (rev) ; (interactive (list (completing-read "Which revision? " ; revision-tag-alist nil t))) ; (let ((pair (assoc rev revision-tag-alist))) ; (or pair ; (error "Invalid revision `%s'" rev)) ; (goto-char (cdr pair)))) ;;;;;;;;;;;;;;;; (defvar rcsview-rlog-mode-hook nil) (defun rcsview-rlog-mode () (setq major-mode 'rcsview-rlog-mode) (setq mode-name "Rcsview-Rlog") (make-local-variable 'rcs-file) (run-hooks 'rcsview-rlog-mode-hook)) ;;;;;;;;;;;;;;;; (defun rcsview-rlog-buffer-parse () (let ((tag-obarray (rcsview-prop-get rcs-file 'tag-obarray)) (rev-obarray (rcsview-prop-get rcs-file 'rev-obarray))) (save-excursion ;; head: 1.60 (cond ((re-search-forward "^head: \\([0-9\.]+\\)" nil t) (rcsview-prop-set rcs-file 'head-revision (intern (buffer-match-string 1) rev-obarray)) (goto-char (match-end 0)))) ;; branch: (cond ((re-search-forward "^branch: \\([0-9\.]+\\)" nil t) (rcsview-prop-set rcs-file 'default-branch (rcsview-revision-vector (buffer-match-string 1))) (goto-char (match-end 0)))) ;; symbolic names: ;; RELENG_4_0_0_RELEASE: 1.54 ;; RELENG_4: 1.54.0.2 (cond ((re-search-forward "^symbolic names:\n" nil t) (let ((tag) (rev) (sym) (tag-list nil)) (while (looking-at "^\t\\([^: ]+\\): \\([0-9\.]+\\)\n") (goto-char (match-end 0)) (setq tag (buffer-match-string 1)) (setq rev (rcsview-revision-vector (buffer-match-string 2))) (setq sym (intern tag tag-obarray)) (put sym 'rev rev) (setq tag-list (cons sym tag-list))) (rcsview-prop-set rcs-file 'tag-list (reverse tag-list))))) (let ((rev-str) (rev-sym) (rev-list nil) (seppat "^----------------------------\n") (revpat "^revision \\([0-9\.]+\\)\n")) ;; ---------------------------- ;; revision 1.60 ;; date: 2000/04/22 15:03:08; author: dfr; state: Exp; lines: +6 -1 ;; Make sure the driver's ops table has been initialised before calling ;; static methods. ;; ---------------------------- (re-search-forward seppat nil t) (while (re-search-forward revpat nil t) (goto-char (match-end 0)) (setq rev-str (buffer-match-string 1)) (setq rev-sym (intern rev-str rev-obarray)) (put rev-sym 'rev (rcsview-revision-vector rev-str)) (put rev-sym 'date (rcsview-parse-date)) (cond ((looking-at "author: \\([a-zA-Z0-9_]+\\); +") (goto-char (match-end 0)) (put rev-sym 'author (buffer-match-string 1)))) (cond ((looking-at "state: \\([a-zA-Z0-9_]+\\); *") (goto-char (match-end 0)) (put rev-sym 'state (buffer-match-string 1)))) (cond ((looking-at "lines: \\+\\([0-9]+\\) \\-\\([0-9]+\\)") (goto-char (match-end 0)) (put rev-sym 'incr (buffer-match-number 1)) (put rev-sym 'decr (buffer-match-number 2)))) (search-forward "\n") ;; XXX branches (put rev-sym 'mark1 (point)) (re-search-forward seppat nil t) (put rev-sym 'mark2 (match-beginning 0)) (setq rev-list (cons rev-sym rev-list))) (rcsview-prop-set rcs-file 'rev-list (reverse rev-list)))))) ;;; XXX: simple version (defun rcsview-parse-date () (let ((date-list)) (cond ((looking-at "date: \\([0-9/ :]+\\); +") (goto-char (match-end 0)) (setq date-list (mapcar #'string-to-number (split "[/ :]" (buffer-match-string 1)))) (apply 'encode-time (reverse (cons "GMT" date-list))))))) (defun rcsview-string-date (time) (format-time-string "%y/%m/%d %R:%S" time)) (defun rcsview-rlog-buffer-init (file) (let ((rlog-buffer (generate-new-buffer (format "RCS-Rlog %s" (basename file))))) (save-excursion (set-buffer rlog-buffer) (setq buffer-read-only nil) (erase-buffer) (call-process "rlog" nil rlog-buffer nil file) (set-buffer-modified-p nil) (setq buffer-read-only t) (cd (dirname file)) (goto-char (point-min)) (rcsview-rlog-mode) (setq rcs-file file) (rcsview-rlog-buffer-parse) (rcsview-revision-analyze)) rlog-buffer)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; log buffer ;;; (defconst rcsview-log-font-lock-keywords (list '("\\(date:\\|author:\\|state:\\|revision:\\)" 1 font-lock-builtin-face) '("\\(date:\\|author:\\|state:\\) \\([a-zA-Z0-9 /:.]+\\);" 2 font-lock-constant-face) '("revision: \\([0-9.]+\\);" 1 font-lock-function-name-face) '("^\\(pr:\\|[a-z]+ by:\\|[a-z]+ from:\\)" 1 font-lock-keyword-face) )) (defvar rcsview-log-mode-map nil) (cond ((not rcsview-log-mode-map) (setq rcsview-log-mode-map (copy-keymap global-map)) (define-key rcsview-log-mode-map "n" 'rcsview-log-next-revision) (define-key rcsview-log-mode-map "p" 'rcsview-log-previous-revision))) (defvar rcsview-log-mode-hook nil) (defun rcsview-log-mode () (kill-all-local-variables) (setq major-mode 'rcsview-log-mode) (setq mode-name "Rcsview-Log") (use-local-map rcsview-log-mode-map) (make-local-variable 'rcs-file) ;; Font lock support (make-local-variable 'font-lock-defaults) (setq font-lock-defaults '(rcsview-log-font-lock-keywords nil t)) (run-hooks 'rcsview-log-mode-hook)) (defun rcsview-log-buffer-init (file) (let ((l-buffer (generate-new-buffer (format "RCS-Log %s" (basename file)))) (r-buffer (rcsview-prop-get file 'rlog-buffer)) (rev-list (rcsview-prop-get file 'rev-list)) (rev-sym)) (save-excursion (set-buffer l-buffer) (rcsview-log-mode) (setq rcs-file file) (setq buffer-read-only nil) (erase-buffer) (while rev-list (setq rev-sym (car rev-list)) (put rev-sym 'log1 (point)) (insert "Revision: " (rcsview-revision-string (get rev-sym 'rev)) "; " "Date: " (rcsview-string-date (get rev-sym 'date)) "; " "Author: " (get rev-sym 'author) "; " "State: " (get rev-sym 'state) "\n") (insert (save-excursion (set-buffer r-buffer) (buffer-substring (get rev-sym 'mark1) (get rev-sym 'mark2)))) (put rev-sym 'log2 (point)) (setq rev-list (cdr rev-list))) (set-buffer-modified-p nil) (setq buffer-read-only t) (goto-char (point-min))) l-buffer)) (defun rcsview-log-show (rev-sym) (save-current-buffer (set-buffer (rcsview-prop-get rcs-file 'log-buffer)) (narrow-to-region (get rev-sym 'log1) (get rev-sym 'log2)) (goto-char (point-min)))) (defun rcsview-log-next-revision () (interactive) (rcsview-revision-next)) (defun rcsview-log-previous-revision () (interactive) (rcsview-revision-prev)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; properties ;;; (defvar *rcsview-prop-obarray* (make-vector 511 0)) (defun rcsview-prop-get (file prop) (let ((sym (intern file *rcsview-prop-obarray*))) (get sym prop))) (defun rcsview-prop-set (file prop val) (let ((sym (intern file *rcsview-prop-obarray*))) (put sym prop val))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; buffer ;;; (defun rcsview-buffer-alive-p (buffer) (and buffer (buffer-name buffer))) ; killed buffer has no name (defvar *rcsview-buffer-list* '((rlog-buffer rcsview-rlog-buffer-init) ; shold be first (text-buffer rcsview-text-buffer-init) ( log-buffer rcsview-log-buffer-init))) (defun rcsview-buffer-setup (file) (let ((buf) (list *rcsview-buffer-list*) (buf-init)) (while list (setq buf-init (car list)) (setq buf (rcsview-prop-get file (nth 0 buf-init))) (cond ((not (rcsview-buffer-alive-p buf)) (setq buf (funcall (nth 1 buf-init) file)) (rcsview-prop-set file (nth 0 buf-init) buf))) (setq list (cdr list))))) (defun rcsview-buffer-clear () (interactive) (mapatoms #'(lambda (sym) (let ((buf) (list *rcsview-buffer-list*) (buf-init)) (while list (setq buf-init (car list)) (setq buf (get sym (nth 0 buf-init))) (if buf (kill-buffer buf)) (setq list (cdr list))))) *rcsview-prop-obarray*) (setq *rcsview-prop-obarray* (make-vector 511 0))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; window configuration ;;; (defun rcsview-window-configure (l-buffer t-buffer) (delete-other-windows) (switch-to-buffer l-buffer) (split-window nil 9) (other-window 1) (switch-to-buffer t-buffer) (other-window 1)) (defun rcsview-rcs-file (file) (interactive "fRCS file name: ") (setq file (expand-file-name file)) (rcsview-obarray-init file) (rcsview-buffer-setup file) (let ((l-buffer (rcsview-prop-get file 'log-buffer)) (t-buffer (rcsview-prop-get file 'text-buffer))) (rcsview-window-configure l-buffer t-buffer)) (rcsview-revision-update)) ;; end of rcsview.el