mirror of
git://git.sv.gnu.org/emacs.git
synced 2026-01-30 04:10:54 -08:00
*** empty log message ***
This commit is contained in:
parent
afa18a4e5d
commit
5b467bf4e2
10 changed files with 5730 additions and 0 deletions
|
|
@ -1,3 +1,16 @@
|
|||
2000-03-10 Stefan Monnier <monnier@cs.yale.edu>
|
||||
|
||||
* cvs-status, log-edit.el, log-view.el, pcvs-defs.el, pcvs-info.el,
|
||||
pcvs-parse.el, pcvs-util.el, pcvs.el: New files.
|
||||
|
||||
* emacs-lisp/ewoc.el: New file. This is a merge of elib-node.el, dll.el
|
||||
and cookie.el (from Elib) with heavy renaming and other massaging.
|
||||
|
||||
* emacs-lisp/easy-mmode.el (easy-mmode-defmap, easy-mmode-defsyntax):
|
||||
Autoload the functions used.
|
||||
(easy-mmode-define-syntax): Fix CL typo.
|
||||
(easy-mmode-define-derived-mode): Improve the docstring generation.
|
||||
|
||||
2000-03-10 Gerd Moellmann <gerd@gnu.org>
|
||||
|
||||
* textmodes/texinfo.el (texinfo-version): Variable and function
|
||||
|
|
|
|||
523
lisp/cvs-status.el
Normal file
523
lisp/cvs-status.el
Normal file
|
|
@ -0,0 +1,523 @@
|
|||
;;; cvs-status.el --- Major mode for browsing `cvs status' output
|
||||
|
||||
;; Copyright (C) 1999-2000 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Stefan Monnier <monnier@cs.yale.edu>
|
||||
;; Keywords: pcl-cvs cvs status tree
|
||||
;; Version: $Name: $
|
||||
;; Revision: $Id: cvs-status.el,v 1.14 2000/03/05 21:32:21 monnier Exp $
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs 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 2, or (at your option)
|
||||
;; any later version.
|
||||
|
||||
;; GNU Emacs 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 GNU Emacs; see the file COPYING. If not, write to the
|
||||
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
;; Boston, MA 02111-1307, USA.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; Todo:
|
||||
|
||||
;; - Rename to cvs-status-mode.el
|
||||
;; - Somehow allow cvs-status-tree to work on-the-fly
|
||||
|
||||
;;; Code:
|
||||
|
||||
(eval-when-compile (require 'cl))
|
||||
(require 'pcvs-util)
|
||||
|
||||
;;;
|
||||
|
||||
(defgroup cvs-status nil
|
||||
"Major mode for browsing `cvs status' output."
|
||||
:group 'pcl-cvs
|
||||
:prefix "cvs-status-")
|
||||
|
||||
(easy-mmode-defmap cvs-status-mode-map
|
||||
'(("n" . next-line)
|
||||
("N" . cvs-status-next-entry)
|
||||
("\M-n" . cvs-status-next-entry)
|
||||
("p" . previous-line)
|
||||
("P" . cvs-status-prev-entry)
|
||||
("\M-p" . cvs-status-prev-entry)
|
||||
("t" . cvs-status-cvstrees)
|
||||
("T" . cvs-status-trees))
|
||||
"CVS-Status' keymap."
|
||||
:group 'cvs-status
|
||||
:inherit 'cvs-mode-map)
|
||||
|
||||
;;(easy-menu-define cvs-status-menu cvs-status-mode-map
|
||||
;; "Menu for `cvs-status-mode'."
|
||||
;; '("CVS-Status"
|
||||
;; ["Show Tag Trees" cvs-status-tree t]
|
||||
;; ))
|
||||
|
||||
(defvar cvs-status-mode-hook nil
|
||||
"Hook run at the end of `cvs-status-mode'.")
|
||||
|
||||
(defconst cvs-status-tags-leader-re "^ Existing Tags:$")
|
||||
(defconst cvs-status-entry-leader-re "^File: \\(\\S-+\\)\\s-+Status: \\(.+\\)$")
|
||||
(defconst cvs-status-dir-re "^cvs[.ex]* [a-z]+: Examining \\(.+\\)$")
|
||||
(defconst cvs-status-rev-re "[0-9][.0-9]*\\.[.0-9]*[0-9]")
|
||||
(defconst cvs-status-tag-re "[ \t]\\([a-zA-Z][^ \t\n.]*\\)")
|
||||
|
||||
(defconst cvs-status-font-lock-keywords
|
||||
`((,cvs-status-entry-leader-re
|
||||
(1 'cvs-filename-face)
|
||||
(2 'cvs-need-action-face))
|
||||
(,cvs-status-tags-leader-re
|
||||
(,cvs-status-rev-re
|
||||
(save-excursion (re-search-forward "^\n" nil 'move) (point))
|
||||
(progn (re-search-backward cvs-status-tags-leader-re nil t)
|
||||
(forward-line 1))
|
||||
(0 font-lock-comment-face))
|
||||
(,cvs-status-tag-re
|
||||
(save-excursion (re-search-forward "^\n" nil 'move) (point))
|
||||
(progn (re-search-backward cvs-status-tags-leader-re nil t)
|
||||
(forward-line 1))
|
||||
(1 font-lock-function-name-face)))))
|
||||
(defconst cvs-status-font-lock-defaults
|
||||
'(cvs-status-font-lock-keywords t nil nil nil))
|
||||
|
||||
|
||||
(put 'cvs-status-mode 'mode-class 'special)
|
||||
;;;###autoload
|
||||
(autoload 'cvs-status-mode "cvs-status" "Mode used for cvs status output." t)
|
||||
(eval-when-compile (autoload 'easy-mmode-define-derived-mode "easy-mmode"))
|
||||
(easy-mmode-define-derived-mode cvs-status-mode fundamental-mode "CVS-Status"
|
||||
"Mode used for cvs status output."
|
||||
(set (make-local-variable 'font-lock-defaults) cvs-status-font-lock-defaults)
|
||||
(set (make-local-variable 'cvs-minor-wrap-function) 'cvs-status-minor-wrap))
|
||||
|
||||
|
||||
(defun cvs-status-next-entry (n)
|
||||
"Go to the N'th next cvs status entry."
|
||||
(interactive "p")
|
||||
(if (< n 0) (cvs-status-prev-entry (- n))
|
||||
(forward-line 1)
|
||||
(re-search-forward cvs-status-entry-leader-re nil t n)
|
||||
(beginning-of-line)))
|
||||
|
||||
(defun cvs-status-prev-entry (n)
|
||||
"Go to the N'th previous cvs status entry."
|
||||
(interactive "p")
|
||||
(if (< n 0) (cvs-status-next-entry (- n))
|
||||
(forward-line -1)
|
||||
(re-search-backward cvs-status-entry-leader-re nil t n)
|
||||
(beginning-of-line)))
|
||||
|
||||
(defun cvs-status-current-file ()
|
||||
(save-excursion
|
||||
(forward-line 1)
|
||||
(or (re-search-backward cvs-status-entry-leader-re nil t)
|
||||
(re-search-forward cvs-status-entry-leader-re))
|
||||
(let* ((file (match-string 1))
|
||||
(cvsdir (and (re-search-backward cvs-status-dir-re nil t)
|
||||
(match-string 1)))
|
||||
(pcldir (and (re-search-backward cvs-pcl-cvs-dirchange-re nil t)
|
||||
(match-string 1)))
|
||||
(dir ""))
|
||||
(let ((default-directory ""))
|
||||
(when pcldir (setq dir (expand-file-name pcldir dir)))
|
||||
(when cvsdir (setq dir (expand-file-name cvsdir dir)))
|
||||
(expand-file-name file dir)))))
|
||||
|
||||
(defun cvs-status-current-tag ()
|
||||
(save-excursion
|
||||
(let ((pt (point))
|
||||
(col (current-column))
|
||||
(start (progn (re-search-backward cvs-status-tags-leader-re nil t) (point)))
|
||||
(end (progn (re-search-forward "^$" nil t) (point))))
|
||||
(when (and (< start pt) (> end pt))
|
||||
(goto-char pt)
|
||||
(end-of-line)
|
||||
(let ((tag nil) (dist pt) (end (point)))
|
||||
(beginning-of-line)
|
||||
(while (re-search-forward cvs-status-tag-re end t)
|
||||
(let* ((cole (current-column))
|
||||
(colb (save-excursion
|
||||
(goto-char (match-beginning 1)) (current-column)))
|
||||
(ndist (min (abs (- cole col)) (abs (- colb col)))))
|
||||
(when (< ndist dist)
|
||||
(setq dist ndist)
|
||||
(setq tag (match-string 1)))))
|
||||
tag)))))
|
||||
|
||||
(defun cvs-status-minor-wrap (buf f)
|
||||
(let ((data (with-current-buffer buf
|
||||
(cons
|
||||
(cons (cvs-status-current-file)
|
||||
(cvs-status-current-tag))
|
||||
(when (ignore-errors (mark))
|
||||
;; `mark-active' is not provided by XEmacs :-(
|
||||
(save-excursion
|
||||
(goto-char (mark))
|
||||
(cons (cvs-status-current-file)
|
||||
(cvs-status-current-tag))))))))
|
||||
(let ((cvs-branch-prefix (cdar data))
|
||||
(cvs-secondary-branch-prefix (and (cdar data) (cddr data)))
|
||||
(cvs-minor-current-files
|
||||
(cons (caar data)
|
||||
(when (and (cadr data) (not (equal (caar data) (cadr data))))
|
||||
(list (cadr data)))))
|
||||
;; FIXME: I need to force because the fileinfos are UNKNOWN
|
||||
(cvs-force-command "/F"))
|
||||
(funcall f))))
|
||||
|
||||
;;
|
||||
;; Tagelt, tag element
|
||||
;;
|
||||
|
||||
(defstruct (cvs-tag
|
||||
(:constructor nil)
|
||||
(:constructor cvs-tag-make
|
||||
(vlist &optional name type))
|
||||
(:conc-name cvs-tag->))
|
||||
vlist
|
||||
name
|
||||
type)
|
||||
|
||||
(defsubst cvs-status-vl-to-str (vl) (mapconcat 'number-to-string vl "."))
|
||||
|
||||
(defun cvs-tag->string (tag)
|
||||
(if (stringp tag) tag
|
||||
(let ((name (cvs-tag->name tag))
|
||||
(vl (cvs-tag->vlist tag)))
|
||||
(if (null name) (cvs-status-vl-to-str vl)
|
||||
(let ((rev (if vl (concat " (" (cvs-status-vl-to-str vl) ")") "")))
|
||||
(if (consp name) (mapcar (lambda (name) (concat name rev)) name)
|
||||
(concat name rev)))))))
|
||||
|
||||
(defun cvs-tag-compare-1 (vl1 vl2)
|
||||
(cond
|
||||
((and (null vl1) (null vl2)) 'equal)
|
||||
((null vl1) 'more2)
|
||||
((null vl2) 'more1)
|
||||
(t (let ((v1 (car vl1))
|
||||
(v2 (car vl2)))
|
||||
(cond
|
||||
((> v1 v2) 'more1)
|
||||
((< v1 v2) 'more2)
|
||||
(t (cvs-tag-compare-1 (cdr vl1) (cdr vl2))))))))
|
||||
|
||||
(defsubst cvs-tag-compare (tag1 tag2)
|
||||
(cvs-tag-compare-1 (cvs-tag->vlist tag1) (cvs-tag->vlist tag2)))
|
||||
|
||||
(defun cvs-tag-merge (tag1 tag2)
|
||||
"Merge TAG1 and TAG2 into one."
|
||||
(let ((type1 (cvs-tag->type tag1))
|
||||
(type2 (cvs-tag->type tag2))
|
||||
(name1 (cvs-tag->name tag1))
|
||||
(name2 (cvs-tag->name tag2)))
|
||||
(unless (equal (cvs-tag->vlist tag1) (cvs-tag->vlist tag2))
|
||||
(setf (cvs-tag->vlist tag1) nil))
|
||||
(if type1
|
||||
(unless (or (not type2) (equal type1 type2))
|
||||
(setf (cvs-tag->type tag1) nil))
|
||||
(setf (cvs-tag->type tag1) type2))
|
||||
(if name1
|
||||
(setf (cvs-tag->name tag1) (cvs-append name1 name2))
|
||||
(setf (cvs-tag->name tag1) name2))
|
||||
tag1))
|
||||
|
||||
(defun cvs-tree-print (tags printer column)
|
||||
"Print the tree of TAGS where each tag's string is given by PRINTER.
|
||||
PRINTER should accept both a tag (in which case it should return a string)
|
||||
or a string (in which case it should simply return its argument).
|
||||
A tag cannot be a CONS. The return value can also be a list of strings,
|
||||
if several nodes where merged into one.
|
||||
The tree will be printed no closer than column COLUMN."
|
||||
|
||||
(let* ((eol (save-excursion (end-of-line) (current-column)))
|
||||
(column (max (+ eol 2) column)))
|
||||
(if (null tags) column
|
||||
;;(move-to-column-force column)
|
||||
(let* ((rev (cvs-car tags))
|
||||
(name (funcall printer (cvs-car rev)))
|
||||
(rest (append (cvs-cdr name) (cvs-cdr tags)))
|
||||
(prefix
|
||||
(save-excursion
|
||||
(or (= (forward-line 1) 0) (insert "\n"))
|
||||
(cvs-tree-print rest printer column))))
|
||||
(assert (>= prefix column))
|
||||
(move-to-column prefix t)
|
||||
(assert (eolp))
|
||||
(insert (cvs-car name))
|
||||
(dolist (br (cvs-cdr rev))
|
||||
(let* ((column (current-column))
|
||||
(brrev (funcall printer (cvs-car br)))
|
||||
(brlength (length (cvs-car brrev)))
|
||||
(brfill (concat (make-string (/ brlength 2) ? ) "|"))
|
||||
(prefix
|
||||
(save-excursion
|
||||
(insert " -- ")
|
||||
(cvs-tree-print (cvs-append brrev brfill (cvs-cdr br))
|
||||
printer (current-column)))))
|
||||
(delete-region (save-excursion (move-to-column prefix) (point))
|
||||
(point))
|
||||
(insert " " (make-string (- prefix column 2) ?-) " ")
|
||||
(end-of-line)))
|
||||
prefix))))
|
||||
|
||||
(defun cvs-tree-merge (tree1 tree2)
|
||||
"Merge tags trees TREE1 and TREE2 into one.
|
||||
BEWARE: because of stability issues, this is not a symetric operation."
|
||||
(assert (and (listp tree1) (listp tree2)))
|
||||
(cond
|
||||
((null tree1) tree2)
|
||||
((null tree2) tree1)
|
||||
(t
|
||||
(let* ((rev1 (car tree1))
|
||||
(tag1 (cvs-car rev1))
|
||||
(vl1 (cvs-tag->vlist tag1))
|
||||
(l1 (length vl1))
|
||||
(rev2 (car tree2))
|
||||
(tag2 (cvs-car rev2))
|
||||
(vl2 (cvs-tag->vlist tag2))
|
||||
(l2 (length vl2)))
|
||||
(cond
|
||||
((= l1 l2)
|
||||
(case (cvs-tag-compare tag1 tag2)
|
||||
(more1 (list* rev2 (cvs-tree-merge tree1 (cdr tree2))))
|
||||
(more2 (list* rev1 (cvs-tree-merge (cdr tree1) tree2)))
|
||||
(equal
|
||||
(cons (cons (cvs-tag-merge tag1 tag2)
|
||||
(cvs-tree-merge (cvs-cdr rev1) (cvs-cdr rev2)))
|
||||
(cvs-tree-merge (cdr tree1) (cdr tree2))))))
|
||||
((> l1 l2)
|
||||
(cvs-tree-merge (list (cons (cvs-tag-make (butlast vl1)) tree1)) tree2))
|
||||
((< l1 l2)
|
||||
(cvs-tree-merge tree1 (list (cons (cvs-tag-make (butlast vl2)) tree2)))))))))
|
||||
|
||||
(defun cvs-tag-make-tag (tag)
|
||||
(let ((vl (mapcar 'string-to-number (split-string (third tag) "\\."))))
|
||||
(cvs-tag-make vl (first tag) (intern (second tag)))))
|
||||
|
||||
(defun cvs-tags->tree (tags)
|
||||
"Make a tree out of a list of TAGS."
|
||||
(let ((tags
|
||||
(mapcar (lambda (tag)
|
||||
(let ((tag (cvs-tag-make-tag tag)))
|
||||
(list (if (not (eq (cvs-tag->type tag) 'branch)) tag
|
||||
(list (cvs-tag-make (butlast (cvs-tag->vlist tag)))
|
||||
tag)))))
|
||||
tags)))
|
||||
(while (cdr tags)
|
||||
(let (tl)
|
||||
(while tags
|
||||
(push (cvs-tree-merge (pop tags) (pop tags)) tl))
|
||||
(setq tags (nreverse tl))))
|
||||
(car tags)))
|
||||
|
||||
(defun cvs-status-get-tags ()
|
||||
"Look for a list of tags, read them in and delete them.
|
||||
Returns NIL if there was an empty list of tags and T if there wasn't
|
||||
even a list. Else, return the list of tags where each element of
|
||||
the list is a three-string list TAG, KIND, REV."
|
||||
(let ((tags nil))
|
||||
(if (not (re-search-forward cvs-status-tags-leader-re nil t)) t
|
||||
(forward-char 1)
|
||||
(let ((pt (point))
|
||||
(lastrev nil)
|
||||
(case-fold-search t))
|
||||
(or
|
||||
(looking-at "\\s-+no\\s-+tags")
|
||||
|
||||
(progn ; normal listing
|
||||
(while (looking-at "^[ \t]+\\([^ \t\n]+\\)[ \t]+(\\([a-z]+\\): \\(.+\\))$")
|
||||
(push (list (match-string 1) (match-string 2) (match-string 3)) tags)
|
||||
(forward-line 1))
|
||||
(unless (looking-at "^$") (setq tags nil) (goto-char pt))
|
||||
tags)
|
||||
|
||||
(progn ; cvstree-style listing
|
||||
(while (or (looking-at "^ .+\\(.\\) \\([0-9.]+\\): \\([^\n\t .0-9][^\n\t ]*\\)?$")
|
||||
(and lastrev
|
||||
(looking-at "^ .+\\(\\) \\(8\\)? \\([^\n\t .0-9][^\n\t ]*\\)$")))
|
||||
(setq lastrev (or (match-string 2) lastrev))
|
||||
(push (list (match-string 3)
|
||||
(if (equal (match-string 1) " ") "branch" "revision")
|
||||
lastrev) tags)
|
||||
(forward-line 1))
|
||||
(unless (looking-at "^$") (setq tags nil) (goto-char pt))
|
||||
(setq tags (nreverse tags)))
|
||||
|
||||
(progn ; new tree style listing
|
||||
(let* ((re-lead "[ \t]*\\(-+\\)?\\(|\n?[ \t]+\\)?")
|
||||
(re3 (concat re-lead "\\(\\.\\)?\\(" cvs-status-rev-re "\\)"))
|
||||
(re2 (concat re-lead cvs-status-tag-re "\\(\\)"))
|
||||
(re1 (concat re-lead cvs-status-tag-re
|
||||
" (\\(" cvs-status-rev-re "\\))")))
|
||||
(while (or (looking-at re1) (looking-at re2) (looking-at re3))
|
||||
(push (list (match-string 3)
|
||||
(if (match-string 1) "branch" "revision")
|
||||
(match-string 4)) tags)
|
||||
(goto-char (match-end 0))
|
||||
(when (eolp) (forward-char 1))))
|
||||
(unless (looking-at "^$") (setq tags nil) (goto-char pt))
|
||||
(setq tags (nreverse tags))))
|
||||
|
||||
(delete-region pt (point)))
|
||||
tags)))
|
||||
|
||||
(defvar font-lock-mode)
|
||||
(defun cvs-refontify (beg end)
|
||||
(when (and (boundp 'font-lock-mode)
|
||||
font-lock-mode
|
||||
(fboundp 'font-lock-fontify-region))
|
||||
(font-lock-fontify-region (1- beg) (1+ end))))
|
||||
|
||||
(defun cvs-status-trees ()
|
||||
"Look for a lists of tags, and replace them with trees."
|
||||
(interactive)
|
||||
(save-excursion
|
||||
(goto-char (point-min))
|
||||
(let ((inhibit-read-only t)
|
||||
(tags nil))
|
||||
(while (listp (setq tags (cvs-status-get-tags)))
|
||||
;;(let ((pt (save-excursion (forward-line -1) (point))))
|
||||
(save-restriction
|
||||
(narrow-to-region (point) (point))
|
||||
;;(newline)
|
||||
(cvs-tree-print (cvs-tags->tree tags) 'cvs-tag->string 3))
|
||||
;;(cvs-refontify pt (point))
|
||||
(sit-for 0)
|
||||
;;)
|
||||
))))
|
||||
|
||||
;;;;
|
||||
;;;; CVSTree-style trees
|
||||
;;;;
|
||||
|
||||
;; chars sets. Ripped from cvstree
|
||||
(defvar cvstree-dstr-2byte-ready
|
||||
(when (featurep 'mule)
|
||||
(if (boundp 'current-language-environment)
|
||||
(string= current-language-environment "Japanese")
|
||||
t)) ; mule/emacs-19
|
||||
"*Variable that specifies characters set used in cvstree tree graph.
|
||||
If non-nil, 2byte (Japanese?) characters set is used.
|
||||
If nil, 1byte characters set is used.
|
||||
2byte characters might be available with Mule or Emacs with Mule extension.")
|
||||
|
||||
(defconst cvstree-dstr-char-space
|
||||
(if cvstree-dstr-2byte-ready "$B!!(B" " "))
|
||||
(defconst cvstree-dstr-char-hbar
|
||||
(if cvstree-dstr-2byte-ready "$B(,(B" "-"))
|
||||
(defconst cvstree-dstr-char-vbar
|
||||
(if cvstree-dstr-2byte-ready "$B(-(B" "|"))
|
||||
(defconst cvstree-dstr-char-branch
|
||||
(if cvstree-dstr-2byte-ready "$B(2(B" "+"))
|
||||
(defconst cvstree-dstr-char-eob ;end of branch
|
||||
(if cvstree-dstr-2byte-ready "$B(1(B" "`"))
|
||||
(defconst cvstree-dstr-char-bob ;beginning of branch
|
||||
(if cvstree-dstr-2byte-ready "$B(3(B" "+"))
|
||||
|
||||
(defun cvs-tag-lessp (tag1 tag2)
|
||||
(eq (cvs-tag-compare tag1 tag2) 'more2))
|
||||
|
||||
(defvar cvs-tree-nomerge t)
|
||||
|
||||
(defun cvs-status-cvstrees (&optional arg)
|
||||
"Look for a list of tags, and replace it with a tree.
|
||||
Optional prefix ARG chooses between two representations."
|
||||
(interactive "P")
|
||||
(save-excursion
|
||||
(goto-char (point-min))
|
||||
(let ((inhibit-read-only t)
|
||||
(tags nil)
|
||||
(cvs-tree-nomerge (if arg (not cvs-tree-nomerge) cvs-tree-nomerge)))
|
||||
(while (listp (setq tags (cvs-status-get-tags)))
|
||||
(let ((tags (mapcar 'cvs-tag-make-tag tags))
|
||||
;;(pt (save-excursion (forward-line -1) (point)))
|
||||
)
|
||||
(setq tags (sort tags 'cvs-tag-lessp))
|
||||
(let* ((first (first tags))
|
||||
(prev (if (cvs-tag-p first)
|
||||
(list (first (cvs-tag->vlist first))) nil)))
|
||||
(cvs-tree-tags-insert tags prev)
|
||||
;;(cvs-refontify pt (point))
|
||||
(sit-for 0)))))))
|
||||
|
||||
(defun cvs-tree-tags-insert (tags prev)
|
||||
(when tags
|
||||
(let* ((tag (car tags))
|
||||
(vlist (cvs-tag->vlist tag))
|
||||
(nprev ;"next prev"
|
||||
(let* ((next (cvs-car (cadr tags)))
|
||||
(nprev (if (and cvs-tree-nomerge next
|
||||
(equal vlist (cvs-tag->vlist next)))
|
||||
prev vlist)))
|
||||
(cvs-map (lambda (v p) v) nprev prev)))
|
||||
(after (save-excursion
|
||||
(newline)
|
||||
(cvs-tree-tags-insert (cdr tags) nprev)))
|
||||
(pe t) ;"prev equal"
|
||||
(nas nil)) ;"next afters" to be returned
|
||||
(insert " ")
|
||||
(do* ((vs vlist (cdr vs))
|
||||
(ps prev (cdr ps))
|
||||
(as after (cdr as)))
|
||||
((and (null as) (null vs) (null ps))
|
||||
(let ((revname (cvs-status-vl-to-str vlist)))
|
||||
(if (cvs-every 'identity (cvs-map 'equal prev vlist))
|
||||
(insert (make-string (+ 4 (length revname)) ? )
|
||||
(or (cvs-tag->name tag) ""))
|
||||
(insert " " revname ": " (or (cvs-tag->name tag) "")))))
|
||||
(let* ((eq (and pe (equal (car ps) (car vs))))
|
||||
(next-eq (equal (cadr ps) (cadr vs))))
|
||||
(let* ((na+char
|
||||
(if (car as)
|
||||
(if eq
|
||||
(if next-eq (cons t cvstree-dstr-char-vbar)
|
||||
(cons t cvstree-dstr-char-branch))
|
||||
(cons nil cvstree-dstr-char-bob))
|
||||
(if eq
|
||||
(if next-eq (cons nil cvstree-dstr-char-space)
|
||||
(cons t cvstree-dstr-char-eob))
|
||||
(cons nil (if (and (eq (cvs-tag->type tag) 'branch)
|
||||
(cvs-every 'null as))
|
||||
cvstree-dstr-char-space
|
||||
cvstree-dstr-char-hbar))))))
|
||||
(insert (cdr na+char))
|
||||
(push (car na+char) nas))
|
||||
(setq pe eq)))
|
||||
(nreverse nas))))
|
||||
|
||||
;;;;
|
||||
;;;; Merged trees from different files
|
||||
;;;;
|
||||
|
||||
(defun cvs-tree-fuzzy-merge-1 (trees tree prev)
|
||||
)
|
||||
|
||||
(defun cvs-tree-fuzzy-merge (trees tree)
|
||||
"Do the impossible: merge TREE into TREES."
|
||||
())
|
||||
|
||||
(defun cvs-tree ()
|
||||
"Get tags from the status output and merge tham all into a big tree."
|
||||
(save-excursion
|
||||
(goto-char (point-min))
|
||||
(let ((inhibit-read-only t)
|
||||
(trees (make-vector 31 0)) tree)
|
||||
(while (listp (setq tree (cvs-tags->tree (cvs-status-get-tags))))
|
||||
(cvs-tree-fuzzy-merge trees tree))
|
||||
(erase-buffer)
|
||||
(let ((cvs-tag-print-rev nil))
|
||||
(cvs-tree-print tree 'cvs-tag->string 3)))))
|
||||
|
||||
|
||||
(provide 'cvs-status)
|
||||
|
||||
;;; cvs-status.el ends here
|
||||
620
lisp/emacs-lisp/ewoc.el
Normal file
620
lisp/emacs-lisp/ewoc.el
Normal file
|
|
@ -0,0 +1,620 @@
|
|||
;;; ewoc.el -- Utility to maintain a view of a list of objects in a buffer
|
||||
|
||||
;; Copyright (C) 1991-2000 Free Software Foundation
|
||||
|
||||
;; Author: Per Cederqvist <ceder@lysator.liu.se>
|
||||
;; Inge Wallin <inge@lysator.liu.se>
|
||||
;; Maintainer: monnier@gnu.org
|
||||
;; Created: 3 Aug 1992
|
||||
;; Keywords: extensions, lisp
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs 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 2, or (at your option)
|
||||
;; any later version.
|
||||
|
||||
;; GNU Emacs 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 GNU Emacs; see the file COPYING. If not, write to the
|
||||
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
;; Boston, MA 02111-1307, USA.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; Ewoc Was Once Cookie
|
||||
;; But now it's Emacs' Widget for Object Collections
|
||||
|
||||
;; As the name implies this derives from the `cookie' package (part
|
||||
;; of Elib). The changes are mostly superficial:
|
||||
|
||||
;; - uses CL (and its `defstruct'
|
||||
;; - separate from Elib.
|
||||
;; - uses its own version of a doubly-linked list which allows us
|
||||
;; to merge the elib-wrapper and the elib-node structures into ewoc-node
|
||||
;; - dropping functions not used by PCL-CVS (the only client of ewoc at the
|
||||
;; time of writing)
|
||||
;; - removing unused arguments
|
||||
;; - renaming:
|
||||
;; elib-node ==> ewoc--node
|
||||
;; collection ==> ewoc
|
||||
;; tin ==> ewoc--node
|
||||
;; cookie ==> data or element or elem
|
||||
|
||||
;; Introduction
|
||||
;; ============
|
||||
;;
|
||||
;; Ewoc is a package that implements a connection between an
|
||||
;; dll (a doubly linked list) and the contents of a buffer.
|
||||
;; Possible uses are dired (have all files in a list, and show them),
|
||||
;; buffer-list, kom-prioritize (in the LysKOM elisp client) and
|
||||
;; others. pcl-cvs.el uses ewoc.el.
|
||||
;;
|
||||
;; Ewoc can be considered as the `view' part of a model-view-controller.
|
||||
;;
|
||||
;; A `element' can be any lisp object. When you use the ewoc
|
||||
;; package you specify a pretty-printer, a function that inserts
|
||||
;; a printable representation of the element in the buffer. (The
|
||||
;; pretty-printer should use "insert" and not
|
||||
;; "insert-before-markers").
|
||||
;;
|
||||
;; A `ewoc' consists of a doubly linked list of elements, a
|
||||
;; header, a footer and a pretty-printer. It is displayed at a
|
||||
;; certain point in a certain buffer. (The buffer and point are
|
||||
;; fixed when the ewoc is created). The header and the footer
|
||||
;; are constant strings. They appear before and after the elements.
|
||||
;; (Currently, once set, they can not be changed).
|
||||
;;
|
||||
;; Ewoc does not affect the mode of the buffer in any way. It
|
||||
;; merely makes it easy to connect an underlying data representation
|
||||
;; to the buffer contents.
|
||||
;;
|
||||
;; A `ewoc--node' is an object that contains one element. There are
|
||||
;; functions in this package that given an ewoc--node extracts the data, or
|
||||
;; gives the next or previous ewoc--node. (All ewoc--nodes are linked together
|
||||
;; in a doubly linked list. The 'previous' ewoc--node is the one that appears
|
||||
;; before the other in the buffer.) You should not do anything with
|
||||
;; an ewoc--node except pass it to the functions in this package.
|
||||
;;
|
||||
;; An ewoc is a very dynamic thing. You can easily add or delete elements.
|
||||
;; You can apply a function to all elements in an ewoc, etc, etc.
|
||||
;;
|
||||
;; Remember that an element can be anything. Your imagination is the
|
||||
;; limit! It is even possible to have another ewoc as an
|
||||
;; element. In that way some kind of tree hierarchy can be created.
|
||||
;;
|
||||
;; Full documentation will, God willing, soon be available in a
|
||||
;; Texinfo manual.
|
||||
|
||||
;; In the mean time `grep '^(.*ewoc-[^-]' emacs-lisp/ewoc.el' can help
|
||||
;; you find all the exported functions:
|
||||
;;
|
||||
;; (defun ewoc-create (buffer pretty-printer &optional header footer pos)
|
||||
;; (defalias 'ewoc-data 'ewoc--node-data)
|
||||
;; (defun ewoc-enter-first (ewoc data)
|
||||
;; (defun ewoc-enter-last (ewoc data)
|
||||
;; (defun ewoc-enter-after (ewoc node data)
|
||||
;; (defun ewoc-enter-before (ewoc node data)
|
||||
;; (defun ewoc-next (ewoc node)
|
||||
;; (defun ewoc-prev (ewoc node)
|
||||
;; (defun ewoc-nth (ewoc n)
|
||||
;; (defun ewoc-map (map-function ewoc &rest args)
|
||||
;; (defun ewoc-filter (ewoc predicate &rest args)
|
||||
;; (defun ewoc-locate (ewoc pos &optional guess)
|
||||
;; (defun ewoc-invalidate (ewoc &rest nodes)
|
||||
;; (defun ewoc-goto-prev (ewoc pos arg)
|
||||
;; (defun ewoc-goto-next (ewoc pos arg)
|
||||
;; (defun ewoc-goto-node (ewoc node)
|
||||
;; (defun ewoc-refresh (ewoc)
|
||||
;; (defun ewoc-collect (ewoc predicate &rest args)
|
||||
;; (defun ewoc-buffer (ewoc)
|
||||
|
||||
|
||||
;; Coding conventions
|
||||
;; ==================
|
||||
;;
|
||||
;; All functions of course start with `ewoc'. Functions and macros
|
||||
;; starting with the prefix `ewoc--' are meant for internal use,
|
||||
;; while those starting with `ewoc-' are exported for public use.
|
||||
;; There are currently no global or buffer-local variables used.
|
||||
|
||||
|
||||
;;; Code:
|
||||
|
||||
(eval-when-compile (require 'cl)) ;because of CL compiler macros
|
||||
|
||||
;; The doubly linked list is implemented as a circular list
|
||||
;; with a dummy node first and last. The dummy node is used as
|
||||
;; "the dll" (or rather is the dll handle passed around).
|
||||
|
||||
(defstruct (ewoc--node
|
||||
(:type vector) ;required for ewoc--node-branch hack
|
||||
(:constructor ewoc--node-create (start-marker data)))
|
||||
left right data start-marker)
|
||||
|
||||
(defalias 'ewoc--node-branch 'aref)
|
||||
|
||||
(defun ewoc--dll-create ()
|
||||
"Create an empty doubly linked list."
|
||||
(let ((dummy-node (ewoc--node-create 'DL-LIST 'DL-LIST)))
|
||||
(setf (ewoc--node-right dummy-node) dummy-node)
|
||||
(setf (ewoc--node-left dummy-node) dummy-node)
|
||||
dummy-node))
|
||||
|
||||
(defun ewoc--node-enter-before (node elemnode)
|
||||
"Insert ELEMNODE before NODE in a DLL."
|
||||
(assert (and (null (ewoc--node-left elemnode)) (null (ewoc--node-right elemnode))))
|
||||
(setf (ewoc--node-left elemnode) (ewoc--node-left node))
|
||||
(setf (ewoc--node-right elemnode) node)
|
||||
(setf (ewoc--node-right (ewoc--node-left node)) elemnode)
|
||||
(setf (ewoc--node-left node) elemnode))
|
||||
|
||||
(defun ewoc--node-enter-first (dll node)
|
||||
"Add a free floating NODE first in DLL."
|
||||
(ewoc--node-enter-before (ewoc--node-right dll) node))
|
||||
|
||||
(defun ewoc--node-enter-last (dll node)
|
||||
"Add a free floating NODE last in DLL."
|
||||
(ewoc--node-enter-before dll node))
|
||||
|
||||
(defun ewoc--node-next (dll node)
|
||||
"Return the node after NODE, or nil if NODE is the last node."
|
||||
(unless (eq (ewoc--node-right node) dll) (ewoc--node-right node)))
|
||||
|
||||
(defun ewoc--node-prev (dll node)
|
||||
"Return the node before NODE, or nil if NODE is the first node."
|
||||
(unless (eq (ewoc--node-left node) dll) (ewoc--node-left node)))
|
||||
|
||||
(defun ewoc--node-delete (node)
|
||||
"Unbind NODE from its doubly linked list and return it."
|
||||
;; This is a no-op when applied to the dummy node. This will return
|
||||
;; nil if applied to the dummy node since it always contains nil.
|
||||
(setf (ewoc--node-right (ewoc--node-left node)) (ewoc--node-right node))
|
||||
(setf (ewoc--node-left (ewoc--node-right node)) (ewoc--node-left node))
|
||||
(setf (ewoc--node-left node) nil)
|
||||
(setf (ewoc--node-right node) nil)
|
||||
node)
|
||||
|
||||
(defun ewoc--node-nth (dll n)
|
||||
"Return the Nth node from the doubly linked list DLL.
|
||||
N counts from zero. If DLL is not that long, nil is returned.
|
||||
If N is negative, return the -(N+1)th last element.
|
||||
Thus, (ewoc--node-nth dll 0) returns the first node,
|
||||
and (ewoc--node-nth dll -1) returns the last node."
|
||||
;; Branch 0 ("follow left pointer") is used when n is negative.
|
||||
;; Branch 1 ("follow right pointer") is used otherwise.
|
||||
(let* ((branch (if (< n 0) 0 1))
|
||||
(node (ewoc--node-branch dll branch)))
|
||||
(if (< n 0) (setq n (- -1 n)))
|
||||
(while (and (not (eq dll node)) (> n 0))
|
||||
(setq node (ewoc--node-branch node branch))
|
||||
(setq n (1- n)))
|
||||
(unless (eq dll node) node)))
|
||||
|
||||
|
||||
;;; The ewoc data type
|
||||
|
||||
(defstruct (ewoc
|
||||
(:constructor nil)
|
||||
(:constructor ewoc--create
|
||||
(buffer pretty-printer header footer dll))
|
||||
(:conc-name ewoc--))
|
||||
buffer pretty-printer header footer dll last-node)
|
||||
|
||||
(defmacro ewoc--set-buffer-bind-dll-let* (ewoc varlist &rest forms)
|
||||
"Execute FORMS with ewoc--buffer selected as current buffer,
|
||||
dll bound to ewoc--dll, and VARLIST bound as in a let*.
|
||||
dll will be bound when VARLIST is initialized, but the current
|
||||
buffer will *not* have been changed.
|
||||
Return value of last form in FORMS."
|
||||
(let ((old-buffer (make-symbol "old-buffer"))
|
||||
(hnd (make-symbol "ewoc")))
|
||||
(` (let* (((, old-buffer) (current-buffer))
|
||||
((, hnd) (, ewoc))
|
||||
(dll (ewoc--dll (, hnd)))
|
||||
(,@ varlist))
|
||||
(set-buffer (ewoc--buffer (, hnd)))
|
||||
(unwind-protect
|
||||
(progn (,@ forms))
|
||||
(set-buffer (, old-buffer)))))))
|
||||
|
||||
(defmacro ewoc--set-buffer-bind-dll (ewoc &rest forms)
|
||||
`(ewoc--set-buffer-bind-dll-let* ,ewoc nil ,@forms))
|
||||
|
||||
(defsubst ewoc--filter-hf-nodes (ewoc node)
|
||||
"Evaluate NODE once and return it.
|
||||
BUT if it is the header or the footer in EWOC return nil instead."
|
||||
(unless (or (eq node (ewoc--header ewoc))
|
||||
(eq node (ewoc--footer ewoc)))
|
||||
node))
|
||||
|
||||
|
||||
(defun ewoc--create-special-node (data string pos)
|
||||
"Insert STRING at POS in current buffer. Remember the start
|
||||
position. Create a wrapper containing that start position and the
|
||||
element DATA."
|
||||
(save-excursion
|
||||
;; Remember the position as a number so that it doesn't move
|
||||
;; when we insert the string.
|
||||
(when (markerp pos) (setq pos (marker-position pos)))
|
||||
(goto-char pos)
|
||||
(let ((inhibit-read-only t))
|
||||
;; Use insert-before-markers so that the marker for the
|
||||
;; next element is updated.
|
||||
(insert-before-markers string)
|
||||
;; Always insert a newline. You want invisible elements? You
|
||||
;; lose. (At least in this version). FIXME-someday. (It is
|
||||
;; harder to fix than it might seem. All markers have to point
|
||||
;; to the right place all the time...)
|
||||
(insert-before-markers ?\n)
|
||||
(ewoc--node-create (copy-marker pos) data))))
|
||||
|
||||
|
||||
(defun ewoc--create-node (data pretty-printer pos)
|
||||
"Call PRETTY-PRINTER with point set at POS in current buffer.
|
||||
Remember the start position. Create a wrapper containing that
|
||||
start position and the element DATA."
|
||||
(save-excursion
|
||||
;; Remember the position as a number so that it doesn't move
|
||||
;; when we insert the string.
|
||||
(when (markerp pos) (setq pos (marker-position pos)))
|
||||
(goto-char pos)
|
||||
(let ((inhibit-read-only t))
|
||||
;; Insert the trailing newline using insert-before-markers
|
||||
;; so that the start position for the next element is updated.
|
||||
(insert-before-markers ?\n)
|
||||
;; Move back, and call the pretty-printer.
|
||||
(backward-char 1)
|
||||
(funcall pretty-printer data)
|
||||
(ewoc--node-create (copy-marker pos) data))))
|
||||
|
||||
|
||||
(defun ewoc--delete-node-internal (ewoc node)
|
||||
"Delete a data string from EWOC.
|
||||
Can not be used on the footer. Returns the wrapper that is deleted.
|
||||
The start-marker in the wrapper is set to nil, so that it doesn't
|
||||
consume any more resources."
|
||||
(let ((dll (ewoc--dll ewoc))
|
||||
(inhibit-read-only t))
|
||||
;; If we are about to delete the node pointed at by last-node,
|
||||
;; set last-node to nil.
|
||||
(if (eq (ewoc--last-node ewoc) node)
|
||||
(setf (ewoc--last-node ewoc) nil))
|
||||
|
||||
(delete-region (ewoc--node-start-marker node)
|
||||
(ewoc--node-start-marker (ewoc--node-next dll node)))
|
||||
(set-marker (ewoc--node-start-marker node) nil)
|
||||
;; Delete the node, and return the wrapper.
|
||||
(ewoc--node-delete node)))
|
||||
|
||||
|
||||
(defvar dll) ;passed by dynamic binding
|
||||
|
||||
(defun ewoc--refresh-node (ewoc node)
|
||||
"Redisplay the element represented by NODE.
|
||||
Can not be used on the footer. dll *must* be bound to
|
||||
\(ewoc--dll ewoc)."
|
||||
(let ((inhibit-read-only t))
|
||||
(save-excursion
|
||||
;; First, remove the string from the buffer:
|
||||
(delete-region (ewoc--node-start-marker node)
|
||||
(1- (marker-position
|
||||
(ewoc--node-start-marker (ewoc--node-next dll node)))))
|
||||
;; Calculate and insert the string.
|
||||
(goto-char (ewoc--node-start-marker node))
|
||||
(funcall (ewoc--pretty-printer ewoc)
|
||||
(ewoc--node-data node)))))
|
||||
|
||||
;;; ===========================================================================
|
||||
;;; Public members of the Ewoc package
|
||||
|
||||
|
||||
(defun ewoc-create (buffer pretty-printer &optional header footer pos)
|
||||
"Create an empty ewoc.
|
||||
|
||||
The ewoc will be inserted in BUFFER. BUFFER may be a
|
||||
buffer or a buffer name. It is created if it does not exist.
|
||||
|
||||
PRETTY-PRINTER should be a function that takes one argument, an
|
||||
element, and inserts a string representing it in the buffer (at
|
||||
point). The string PRETTY-PRINTER inserts may be empty or span
|
||||
several linse. A trailing newline will always be inserted
|
||||
automatically. The PRETTY-PRINTER should use insert, and not
|
||||
insert-before-markers.
|
||||
|
||||
Optional third argument HEADER is a string that will always be
|
||||
present at the top of the ewoc. HEADER should end with a
|
||||
newline. Optionaly fourth argument FOOTER is similar, and will
|
||||
always be inserted at the bottom of the ewoc.
|
||||
|
||||
Optional fifth argument POS is a buffer position, specifying
|
||||
where the ewoc will be inserted. It defaults to the
|
||||
beginning of the buffer."
|
||||
(let ((new-ewoc
|
||||
(ewoc--create (get-buffer-create buffer)
|
||||
pretty-printer nil nil (ewoc--dll-create))))
|
||||
(ewoc--set-buffer-bind-dll new-ewoc
|
||||
;; Set default values
|
||||
(unless header (setq header ""))
|
||||
(unless footer (setq footer ""))
|
||||
(unless pos (setq pos (point-min)))
|
||||
;; Force header to be above footer.
|
||||
(if (markerp pos) (setq pos (marker-position pos)))
|
||||
(let ((foot (ewoc--create-special-node footer footer pos))
|
||||
(head (ewoc--create-special-node header header pos)))
|
||||
(ewoc--node-enter-first dll head)
|
||||
(ewoc--node-enter-last dll foot)
|
||||
(setf (ewoc--header new-ewoc) (ewoc--node-nth dll 0))
|
||||
(setf (ewoc--footer new-ewoc) (ewoc--node-nth dll -1))))
|
||||
;; Return the ewoc
|
||||
new-ewoc))
|
||||
|
||||
(defalias 'ewoc-data 'ewoc--node-data)
|
||||
|
||||
(defun ewoc-enter-first (ewoc data)
|
||||
"Enter DATA first in EWOC."
|
||||
(ewoc--set-buffer-bind-dll ewoc
|
||||
(ewoc-enter-after ewoc (ewoc--node-nth dll 0) data)))
|
||||
|
||||
(defun ewoc-enter-last (ewoc data)
|
||||
"Enter DATA last in EWOC."
|
||||
(ewoc--set-buffer-bind-dll ewoc
|
||||
(ewoc-enter-before ewoc (ewoc--node-nth dll -1) data)))
|
||||
|
||||
|
||||
(defun ewoc-enter-after (ewoc node data)
|
||||
"Enter a new element DATA after NODE in EWOC."
|
||||
(ewoc--set-buffer-bind-dll ewoc
|
||||
(ewoc-enter-before ewoc (ewoc--node-next dll node) data)))
|
||||
|
||||
(defun ewoc-enter-before (ewoc node data)
|
||||
"Enter a new element DATA before NODE in EWOC."
|
||||
(ewoc--set-buffer-bind-dll ewoc
|
||||
(ewoc--node-enter-before
|
||||
node
|
||||
(ewoc--create-node
|
||||
data
|
||||
(ewoc--pretty-printer ewoc)
|
||||
(ewoc--node-start-marker node)))))
|
||||
|
||||
(defun ewoc-next (ewoc node)
|
||||
"Get the next node.
|
||||
Returns nil if NODE is nil or the last element."
|
||||
(when node
|
||||
(ewoc--filter-hf-nodes
|
||||
ewoc (ewoc--node-next (ewoc--dll ewoc) node))))
|
||||
|
||||
(defun ewoc-prev (ewoc node)
|
||||
"Get the previous node.
|
||||
Returns nil if NODE is nil or the first element."
|
||||
(when node
|
||||
(ewoc--filter-hf-nodes
|
||||
ewoc
|
||||
(ewoc--node-prev (ewoc--dll ewoc) node))))
|
||||
|
||||
|
||||
(defun ewoc-nth (ewoc n)
|
||||
"Return the Nth node.
|
||||
N counts from zero. Nil is returned if there is less than N elements.
|
||||
If N is negative, return the -(N+1)th last element.
|
||||
Thus, (ewoc-nth dll 0) returns the first node,
|
||||
and (ewoc-nth dll -1) returns the last node.
|
||||
Use `ewoc--node-data' to extract the data from the node."
|
||||
;; Skip the header (or footer, if n is negative).
|
||||
(setq n (if (< n 0) (1- n) (1+ n)))
|
||||
(ewoc--filter-hf-nodes ewoc
|
||||
(ewoc--node-nth (ewoc--dll ewoc) n)))
|
||||
|
||||
(defun ewoc-map (map-function ewoc &rest args)
|
||||
"Apply MAP-FUNCTION to all elements in EWOC.
|
||||
MAP-FUNCTION is applied to the first element first.
|
||||
If MAP-FUNCTION returns non-nil the element will be refreshed (its
|
||||
pretty-printer will be called once again).
|
||||
|
||||
Note that the buffer for EWOC will be current buffer when MAP-FUNCTION
|
||||
is called. MAP-FUNCTION must restore the current buffer to BUFFER before
|
||||
it returns, if it changes it.
|
||||
|
||||
If more than two arguments are given, the remaining
|
||||
arguments will be passed to MAP-FUNCTION."
|
||||
(ewoc--set-buffer-bind-dll-let* ewoc
|
||||
((footer (ewoc--footer ewoc))
|
||||
(node (ewoc--node-nth dll 1)))
|
||||
(while (not (eq node footer))
|
||||
(if (apply map-function (ewoc--node-data node) args)
|
||||
(ewoc--refresh-node ewoc node))
|
||||
(setq node (ewoc--node-next dll node)))))
|
||||
|
||||
(defun ewoc-filter (ewoc predicate &rest args)
|
||||
"Remove all elements in EWOC for which PREDICATE returns nil.
|
||||
Note that the buffer for EWOC will be current-buffer when PREDICATE
|
||||
is called. PREDICATE must restore the current buffer before it returns
|
||||
if it changes it.
|
||||
The PREDICATE is called with the element as its first argument. If any
|
||||
ARGS are given they will be passed to the PREDICATE."
|
||||
(ewoc--set-buffer-bind-dll-let* ewoc
|
||||
((node (ewoc--node-nth dll 1))
|
||||
(footer (ewoc--footer ewoc))
|
||||
(next nil))
|
||||
(while (not (eq node footer))
|
||||
(setq next (ewoc--node-next dll node))
|
||||
(unless (apply predicate (ewoc--node-data node) args)
|
||||
(ewoc--delete-node-internal ewoc node))
|
||||
(setq node next))))
|
||||
|
||||
(defun ewoc-locate (ewoc pos &optional guess)
|
||||
"Return the node that POS (a buffer position) is within.
|
||||
POS may be a marker or an integer.
|
||||
GUESS should be a node that it is likely that POS is near.
|
||||
|
||||
If POS points before the first element, the first node is returned.
|
||||
If POS points after the last element, the last node is returned.
|
||||
If the EWOC is empty, nil is returned."
|
||||
(ewoc--set-buffer-bind-dll-let* ewoc
|
||||
((footer (ewoc--footer ewoc)))
|
||||
|
||||
(cond
|
||||
;; Nothing present?
|
||||
((eq (ewoc--node-nth dll 1) (ewoc--node-nth dll -1))
|
||||
nil)
|
||||
|
||||
;; Before second elem?
|
||||
((< pos (ewoc--node-start-marker (ewoc--node-nth dll 2)))
|
||||
(ewoc--node-nth dll 1))
|
||||
|
||||
;; After one-before-last elem?
|
||||
((>= pos (ewoc--node-start-marker (ewoc--node-nth dll -2)))
|
||||
(ewoc--node-nth dll -2))
|
||||
|
||||
;; We now know that pos is within a elem.
|
||||
(t
|
||||
;; Make an educated guess about which of the three known
|
||||
;; node'es (the first, the last, or GUESS) is nearest.
|
||||
(let* ((best-guess (ewoc--node-nth dll 1))
|
||||
(distance (abs (- pos (ewoc--node-start-marker best-guess)))))
|
||||
(when guess
|
||||
(let ((d (abs (- pos (ewoc--node-start-marker guess)))))
|
||||
(when (< d distance)
|
||||
(setq distance d)
|
||||
(setq best-guess guess))))
|
||||
|
||||
(let* ((g (ewoc--node-nth dll -1)) ;Check the last elem
|
||||
(d (abs (- pos (ewoc--node-start-marker g)))))
|
||||
(when (< d distance)
|
||||
(setq distance d)
|
||||
(setq best-guess g)))
|
||||
|
||||
(when (ewoc--last-node ewoc) ;Check "previous".
|
||||
(let* ((g (ewoc--last-node ewoc))
|
||||
(d (abs (- pos (ewoc--node-start-marker g)))))
|
||||
(when (< d distance)
|
||||
(setq distance d)
|
||||
(setq best-guess g))))
|
||||
|
||||
;; best-guess is now a "best guess".
|
||||
;; Find the correct node. First determine in which direction
|
||||
;; it lies, and then move in that direction until it is found.
|
||||
|
||||
(cond
|
||||
;; Is pos after the guess?
|
||||
((>= pos
|
||||
(ewoc--node-start-marker best-guess))
|
||||
;; Loop until we are exactly one node too far down...
|
||||
(while (>= pos (ewoc--node-start-marker best-guess))
|
||||
(setq best-guess (ewoc--node-next dll best-guess)))
|
||||
;; ...and return the previous node.
|
||||
(ewoc--node-prev dll best-guess))
|
||||
|
||||
;; Pos is before best-guess
|
||||
(t
|
||||
(while (< pos (ewoc--node-start-marker best-guess))
|
||||
(setq best-guess (ewoc--node-prev dll best-guess)))
|
||||
best-guess)))))))
|
||||
|
||||
(defun ewoc-invalidate (ewoc &rest nodes)
|
||||
"Refresh some elements.
|
||||
The pretty-printer that for EWOC will be called for all NODES."
|
||||
(ewoc--set-buffer-bind-dll ewoc
|
||||
(dolist (node nodes)
|
||||
(ewoc--refresh-node ewoc node))))
|
||||
|
||||
(defun ewoc-goto-prev (ewoc pos arg)
|
||||
"Move point to the ARGth previous element.
|
||||
Don't move if we are at the first element, or if EWOC is empty.
|
||||
Returns the node we moved to."
|
||||
(ewoc--set-buffer-bind-dll-let* ewoc
|
||||
((node (ewoc-locate ewoc pos (ewoc--last-node ewoc))))
|
||||
(when node
|
||||
(while (and node (> arg 0))
|
||||
(setq arg (1- arg))
|
||||
(setq node (ewoc--node-prev dll node)))
|
||||
;; Never step above the first element.
|
||||
(unless (ewoc--filter-hf-nodes ewoc node)
|
||||
(setq node (ewoc--node-nth dll 1)))
|
||||
(ewoc-goto-node ewoc node))))
|
||||
|
||||
(defun ewoc-goto-next (ewoc pos arg)
|
||||
"Move point to the ARGth next element.
|
||||
Don't move if we are at the last element.
|
||||
Returns the node."
|
||||
(ewoc--set-buffer-bind-dll-let* ewoc
|
||||
((node (ewoc-locate ewoc pos (ewoc--last-node ewoc))))
|
||||
(while (and node (> arg 0))
|
||||
(setq arg (1- arg))
|
||||
(setq node (ewoc--node-next dll node)))
|
||||
;; Never step below the first element.
|
||||
(unless (ewoc--filter-hf-nodes ewoc node)
|
||||
(setq node (ewoc--node-nth dll -2)))
|
||||
(ewoc-goto-node ewoc node)))
|
||||
|
||||
(defun ewoc-goto-node (ewoc node)
|
||||
"Move point to NODE."
|
||||
(ewoc--set-buffer-bind-dll ewoc
|
||||
(goto-char (ewoc--node-start-marker node))
|
||||
(if goal-column (move-to-column goal-column))
|
||||
(setf (ewoc--last-node ewoc) node)))
|
||||
|
||||
(defun ewoc-refresh (ewoc)
|
||||
"Refresh all data in EWOC.
|
||||
The pretty-printer that was specified when the EWOC was created
|
||||
will be called for all elements in EWOC.
|
||||
Note that `ewoc-invalidate' is more efficient if only a small
|
||||
number of elements needs to be refreshed."
|
||||
(ewoc--set-buffer-bind-dll-let* ewoc
|
||||
((header (ewoc--header ewoc))
|
||||
(footer (ewoc--footer ewoc)))
|
||||
(let ((inhibit-read-only t))
|
||||
(delete-region (ewoc--node-start-marker (ewoc--node-nth dll 1))
|
||||
(ewoc--node-start-marker footer))
|
||||
(goto-char (ewoc--node-start-marker footer))
|
||||
(let ((node (ewoc--node-nth dll 1)))
|
||||
(while (not (eq node footer))
|
||||
(set-marker (ewoc--node-start-marker node) (point))
|
||||
(funcall (ewoc--pretty-printer ewoc)
|
||||
(ewoc--node-data node))
|
||||
(insert "\n")
|
||||
(setq node (ewoc--node-next dll node)))))
|
||||
(set-marker (ewoc--node-start-marker footer) (point))))
|
||||
|
||||
(defun ewoc-collect (ewoc predicate &rest args)
|
||||
"Select elements from EWOC using PREDICATE.
|
||||
Return a list of all selected data elements.
|
||||
PREDICATE is a function that takes a data element as its first argument.
|
||||
The elements on the returned list will appear in the same order as in
|
||||
the buffer. You should not rely on in which order PREDICATE is
|
||||
called.
|
||||
Note that the buffer the EWOC is displayed in is current-buffer
|
||||
when PREDICATE is called. If PREDICATE must restore current-buffer if
|
||||
it changes it.
|
||||
If more than two arguments are given the
|
||||
remaining arguments will be passed to PREDICATE."
|
||||
(ewoc--set-buffer-bind-dll-let* ewoc
|
||||
((header (ewoc--header ewoc))
|
||||
(node (ewoc--node-nth dll -2))
|
||||
result)
|
||||
(while (not (eq node header))
|
||||
(if (apply predicate (ewoc--node-data node) args)
|
||||
(push (ewoc--node-data node) result))
|
||||
(setq node (ewoc--node-prev dll node)))
|
||||
result))
|
||||
|
||||
(defun ewoc-buffer (ewoc)
|
||||
"Return the buffer that is associated with EWOC.
|
||||
Returns nil if the buffer has been deleted."
|
||||
(let ((buf (ewoc--buffer ewoc)))
|
||||
(when (buffer-name buf) buf)))
|
||||
|
||||
|
||||
(provide 'ewoc)
|
||||
|
||||
;;; Local Variables:
|
||||
;;; eval: (put 'ewoc--set-buffer-bind-dll 'lisp-indent-hook 1)
|
||||
;;; eval: (put 'ewoc--set-buffer-bind-dll-let* 'lisp-indent-hook 2)
|
||||
;;; End:
|
||||
|
||||
;;; ewoc.el ends here
|
||||
448
lisp/log-edit.el
Normal file
448
lisp/log-edit.el
Normal file
|
|
@ -0,0 +1,448 @@
|
|||
;;; log-edit.el --- Major mode for editing CVS commit messages
|
||||
|
||||
;; Copyright (C) 1999-2000 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Stefan Monnier <monnier@cs.yale.edu>
|
||||
;; Keywords: pcl-cvs cvs commit log
|
||||
;; Version: $Name: $
|
||||
;; Revision: $Id: log-edit.el,v 1.8 2000/03/05 21:32:21 monnier Exp $
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs 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 2, or (at your option)
|
||||
;; any later version.
|
||||
|
||||
;; GNU Emacs 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 GNU Emacs; see the file COPYING. If not, write to the
|
||||
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
;; Boston, MA 02111-1307, USA.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; Todo:
|
||||
|
||||
;; - Remove a single leading `* <file>' in log-edit-insert-changelog
|
||||
;; - Move in VC's code
|
||||
;; - Add compatibility for VC's hook variables
|
||||
;; - add compatibility with cvs-edit.el
|
||||
|
||||
;;; Code:
|
||||
|
||||
(eval-when-compile (require 'cl))
|
||||
(require 'add-log) ; for all the ChangeLog goodies
|
||||
(require 'pcvs-util)
|
||||
(require 'ring)
|
||||
(require 'vc)
|
||||
|
||||
;;;;
|
||||
;;;; Global Variables
|
||||
;;;;
|
||||
|
||||
(defgroup log-edit nil
|
||||
"Major mode for editing commit messages for PCL-CVS."
|
||||
:group 'pcl-cvs
|
||||
:prefix "log-edit-")
|
||||
|
||||
;; compiler pacifiers
|
||||
(defvar cvs-buffer)
|
||||
|
||||
(easy-mmode-defmap log-edit-mode-map
|
||||
`(("\C-c\C-c" . log-edit-done)
|
||||
("\C-c\C-a" . log-edit-insert-changelog)
|
||||
("\C-c\C-f" . log-edit-show-files)
|
||||
("\C-c?" . log-edit-mode-help))
|
||||
"Keymap for the `log-edit-mode' (used when editing cvs log messages)."
|
||||
:group 'log-edit
|
||||
:inherit (if (boundp 'vc-log-entry-mode) vc-log-entry-mode))
|
||||
|
||||
(defcustom log-edit-confirm t
|
||||
"*If non-nil, `log-edit-done' will request confirmation.
|
||||
If 'changed, only request confirmation if the list of files has
|
||||
changed since the beginning of the log-edit session."
|
||||
:group 'log-edit
|
||||
:type '(choice (const changed) (const t) (const nil)))
|
||||
|
||||
(defcustom log-edit-keep-buffer nil
|
||||
"*If non-nil, don't hide the buffer after `log-edit-done'."
|
||||
:group 'log-edit
|
||||
:type 'boolean)
|
||||
|
||||
(defvar cvs-commit-buffer-require-final-newline t
|
||||
"Obsolete, use `log-edit-require-final-newline'.")
|
||||
|
||||
(defcustom log-edit-require-final-newline
|
||||
cvs-commit-buffer-require-final-newline
|
||||
"*Enforce a newline at the end of commit log messages.
|
||||
Enforce it silently if t, query if non-nil and don't do anything if nil."
|
||||
:group 'log-edit
|
||||
:type '(choice (const ask) (const t) (const nil)))
|
||||
|
||||
(defcustom log-edit-setup-invert nil
|
||||
"*Non-nil means `log-edit' should invert the meaning of its SETUP arg.
|
||||
If SETUP is 'force, this variable has no effect."
|
||||
:group 'log-edit
|
||||
:type 'boolean)
|
||||
|
||||
(defcustom log-edit-hook '(log-edit-insert-cvs-template
|
||||
log-edit-insert-changelog)
|
||||
"*Hook run at the end of `log-edit'."
|
||||
:group 'log-edit
|
||||
:type '(hook :options (log-edit-insert-cvs-template
|
||||
log-edit-insert-changelog)))
|
||||
|
||||
(defcustom log-edit-mode-hook nil
|
||||
"*Hook run when entering `log-edit-mode'."
|
||||
:group 'log-edit
|
||||
:type 'hook)
|
||||
|
||||
(defcustom log-edit-done-hook nil
|
||||
"*Hook run before doing the actual commit.
|
||||
This hook can be used to cleanup the message, enforce various
|
||||
conventions, or to allow recording the message in some other database,
|
||||
such as a bug-tracking system. The list of files about to be committed
|
||||
can be obtained from `log-edit-files'."
|
||||
:group 'log-edit
|
||||
:type '(hook :options (log-edit-delete-common-indentation
|
||||
log-edit-add-to-changelog)))
|
||||
|
||||
(defvar cvs-changelog-full-paragraphs t
|
||||
"*If non-nil, include full ChangeLog paragraphs in the CVS log.
|
||||
This may be set in the ``local variables'' section of a ChangeLog, to
|
||||
indicate the policy for that ChangeLog.
|
||||
|
||||
A ChangeLog paragraph is a bunch of log text containing no blank lines;
|
||||
a paragraph usually describes a set of changes with a single purpose,
|
||||
but perhaps spanning several functions in several files. Changes in
|
||||
different paragraphs are unrelated.
|
||||
|
||||
You could argue that the CVS log entry for a file should contain the
|
||||
full ChangeLog paragraph mentioning the change to the file, even though
|
||||
it may mention other files, because that gives you the full context you
|
||||
need to understand the change. This is the behaviour you get when this
|
||||
variable is set to t.
|
||||
|
||||
On the other hand, you could argue that the CVS log entry for a change
|
||||
should contain only the text for the changes which occurred in that
|
||||
file, because the CVS log is per-file. This is the behaviour you get
|
||||
when this variable is set to nil.")
|
||||
|
||||
;;;; Internal global or buffer-local vars
|
||||
|
||||
(defconst log-edit-files-buf "*log-edit-files*")
|
||||
(defvar log-edit-initial-files nil)
|
||||
(defvar log-edit-callback nil)
|
||||
(defvar log-edit-listfun nil)
|
||||
|
||||
;;;;
|
||||
;;;; Actual code
|
||||
;;;;
|
||||
|
||||
;;;###autoload
|
||||
(defun log-edit (callback &optional setup listfun &rest ignore)
|
||||
"Setup a buffer to enter a log message.
|
||||
The buffer will be put in `log-edit-mode'.
|
||||
If SETUP is non-nil, the buffer is then erased and `log-edit-hook' is run.
|
||||
Mark and point will be set around the entire contents of the
|
||||
buffer so that it is easy to kill the contents of the buffer with \\[kill-region].
|
||||
Once you're done editing the message, pressing \\[log-edit-done] will call
|
||||
`log-edit-done' which will end up calling CALLBACK to do the actual commit."
|
||||
(when (and log-edit-setup-invert (not (eq setup 'force)))
|
||||
(setq setup (not setup)))
|
||||
(when setup (erase-buffer))
|
||||
(log-edit-mode)
|
||||
(set (make-local-variable 'log-edit-callback) callback)
|
||||
(set (make-local-variable 'log-edit-listfun) listfun)
|
||||
(when setup (run-hooks 'log-edit-hook))
|
||||
(goto-char (point-min)) (push-mark (point-max))
|
||||
(set (make-local-variable 'log-edit-initial-files) (log-edit-files))
|
||||
(message (substitute-command-keys
|
||||
"Press \\[log-edit-done] when you are done editing.")))
|
||||
|
||||
(define-derived-mode log-edit-mode text-mode "Log-Edit"
|
||||
"Major mode for entering commit messages.
|
||||
This mode is intended for entering messages in a *cvs-commit*
|
||||
buffer when using PCL-CVS. It provides a binding for the
|
||||
\\[log-edit-done] command that should be used when done editing
|
||||
to trigger the actual commit, as well as a few handy support
|
||||
commands.
|
||||
\\{log-edit-mode-map}")
|
||||
|
||||
(defun log-edit-hide-buf (&optional buf where)
|
||||
(when (setq buf (get-buffer (or buf log-edit-files-buf)))
|
||||
(let ((win (get-buffer-window buf where)))
|
||||
(if win (ignore-errors (delete-window win))))
|
||||
(bury-buffer buf)))
|
||||
|
||||
(defun log-edit-done ()
|
||||
"Finish editing the log message and commit the files.
|
||||
This can only be used in the *cvs-commit* buffer.
|
||||
With a prefix argument, prompt for cvs commit flags.
|
||||
If you want to abort the commit, simply delete the buffer."
|
||||
(interactive)
|
||||
(if (and (> (point-max) 1)
|
||||
(/= (char-after (1- (point-max))) ?\n)
|
||||
(or (eq log-edit-require-final-newline t)
|
||||
(and log-edit-require-final-newline
|
||||
(y-or-n-p
|
||||
(format "Buffer %s does not end in newline. Add one? "
|
||||
(buffer-name))))))
|
||||
(save-excursion
|
||||
(goto-char (point-max))
|
||||
(insert ?\n)))
|
||||
(if (boundp 'vc-comment-ring) (ring-insert vc-comment-ring (buffer-string)))
|
||||
(let ((win (get-buffer-window log-edit-files-buf)))
|
||||
(if (and log-edit-confirm
|
||||
(not (and (eq log-edit-confirm 'changed)
|
||||
(equal (log-edit-files) log-edit-initial-files)))
|
||||
(progn
|
||||
(log-edit-show-files)
|
||||
(not (y-or-n-p "Really commit ? "))))
|
||||
(progn (when (not win) (log-edit-hide-buf))
|
||||
(message "Oh, well! Later maybe?"))
|
||||
(run-hooks 'log-edit-done-hook)
|
||||
(log-edit-hide-buf)
|
||||
(unless log-edit-keep-buffer
|
||||
(cvs-bury-buffer (current-buffer)
|
||||
(when (boundp 'cvs-buffer) cvs-buffer)))
|
||||
(call-interactively log-edit-callback))))
|
||||
|
||||
(defun log-edit-files ()
|
||||
"Return the list of files that are about to be committed."
|
||||
(ignore-errors (funcall log-edit-listfun)))
|
||||
|
||||
|
||||
(defun log-edit-insert-changelog ()
|
||||
"Insert a log message by looking at the ChangeLog.
|
||||
The idea is to write your ChangeLog entries first, and then use this
|
||||
command to commit your changes.
|
||||
|
||||
To select default log text, we:
|
||||
- find the ChangeLog entries for the files to be checked in,
|
||||
- verify that the top entry in the ChangeLog is on the current date
|
||||
and by the current user; if not, we don't provide any default text,
|
||||
- search the ChangeLog entry for paragraphs containing the names of
|
||||
the files we're checking in, and finally
|
||||
- use those paragraphs as the log text."
|
||||
(interactive)
|
||||
(cvs-insert-changelog-entries (log-edit-files))
|
||||
(log-edit-delete-common-indentation))
|
||||
|
||||
(defun log-edit-mode-help ()
|
||||
"Provide help for the `log-edit-mode-map'."
|
||||
(interactive)
|
||||
(if (eq last-command 'log-edit-mode-help)
|
||||
(describe-function major-mode)
|
||||
(message
|
||||
(substitute-command-keys
|
||||
"Type `\\[log-edit-done]' to finish commit. Try `\\[describe-function] log-edit-done' for more help."))))
|
||||
|
||||
(defun log-edit-delete-common-indentation ()
|
||||
"Unindent the current buffer rigidly until at least one line is flush left."
|
||||
(save-excursion
|
||||
(let ((common (point-max)))
|
||||
(goto-char (point-min))
|
||||
(while (< (point) (point-max))
|
||||
(if (not (looking-at "^[ \t]*$"))
|
||||
(setq common (min common (current-indentation))))
|
||||
(forward-line 1))
|
||||
(indent-rigidly (point-min) (point-max) (- common)))))
|
||||
|
||||
(defun log-edit-show-files ()
|
||||
"Show the list of files to be committed."
|
||||
(interactive)
|
||||
(let* ((files (log-edit-files))
|
||||
(editbuf (current-buffer))
|
||||
(buf (get-buffer-create "*log-edit-files*")))
|
||||
(with-current-buffer buf
|
||||
(log-edit-hide-buf buf 'all)
|
||||
(setq buffer-read-only nil)
|
||||
(erase-buffer)
|
||||
(insert (mapconcat 'identity files "\n"))
|
||||
(setq buffer-read-only t)
|
||||
(goto-char (point-min))
|
||||
(save-selected-window
|
||||
(cvs-pop-to-buffer-same-frame buf)
|
||||
(shrink-window-if-larger-than-buffer)
|
||||
(selected-window)))))
|
||||
|
||||
(defun log-edit-insert-cvs-template ()
|
||||
"Insert the template specified by the CVS administrator, if any."
|
||||
(interactive)
|
||||
(when (file-readable-p "CVS/Template")
|
||||
(insert-file-contents "CVS/Template")))
|
||||
|
||||
|
||||
(defun log-edit-add-to-changelog ()
|
||||
"Insert this log message into the appropriate ChangeLog file."
|
||||
(interactive)
|
||||
;; Yuck!
|
||||
(unless (string= (buffer-string) (ring-ref vc-comment-ring 0))
|
||||
(ring-insert vc-comment-ring (buffer-string)))
|
||||
(dolist (f (log-edit-files))
|
||||
(let ((buffer-file-name (expand-file-name f)))
|
||||
(save-excursion
|
||||
(vc-comment-to-change-log)))))
|
||||
|
||||
;;;;
|
||||
;;;; functions for getting commit message from ChangeLog a file...
|
||||
;;;; Courtesy Jim Blandy
|
||||
;;;;
|
||||
|
||||
(defun cvs-narrow-changelog ()
|
||||
"Narrow to the top page of the current buffer, a ChangeLog file.
|
||||
Actually, the narrowed region doesn't include the date line.
|
||||
A \"page\" in a ChangeLog file is the area between two dates."
|
||||
(or (eq major-mode 'change-log-mode)
|
||||
(error "cvs-narrow-changelog: current buffer isn't a ChangeLog"))
|
||||
|
||||
(goto-char (point-min))
|
||||
|
||||
;; Skip date line and subsequent blank lines.
|
||||
(forward-line 1)
|
||||
(if (looking-at "[ \t\n]*\n")
|
||||
(goto-char (match-end 0)))
|
||||
|
||||
(let ((start (point)))
|
||||
(forward-page 1)
|
||||
(narrow-to-region start (point))
|
||||
(goto-char (point-min))))
|
||||
|
||||
(defun cvs-changelog-paragraph ()
|
||||
"Return the bounds of the ChangeLog paragraph containing point.
|
||||
If we are between paragraphs, return the previous paragraph."
|
||||
(save-excursion
|
||||
(beginning-of-line)
|
||||
(if (looking-at "^[ \t]*$")
|
||||
(skip-chars-backward " \t\n" (point-min)))
|
||||
(list (progn
|
||||
(if (re-search-backward "^[ \t]*\n" nil 'or-to-limit)
|
||||
(goto-char (match-end 0)))
|
||||
(point))
|
||||
(if (re-search-forward "^[ \t\n]*$" nil t)
|
||||
(match-beginning 0)
|
||||
(point)))))
|
||||
|
||||
(defun cvs-changelog-subparagraph ()
|
||||
"Return the bounds of the ChangeLog subparagraph containing point.
|
||||
A subparagraph is a block of non-blank lines beginning with an asterisk.
|
||||
If we are between sub-paragraphs, return the previous subparagraph."
|
||||
(save-excursion
|
||||
(end-of-line)
|
||||
(if (search-backward "*" nil t)
|
||||
(list (progn (beginning-of-line) (point))
|
||||
(progn
|
||||
(forward-line 1)
|
||||
(if (re-search-forward "^[ \t]*[\n*]" nil t)
|
||||
(match-beginning 0)
|
||||
(point-max))))
|
||||
(list (point) (point)))))
|
||||
|
||||
(defun cvs-changelog-entry ()
|
||||
"Return the bounds of the ChangeLog entry containing point.
|
||||
The variable `cvs-changelog-full-paragraphs' decides whether an
|
||||
\"entry\" is a paragraph or a subparagraph; see its documentation string
|
||||
for more details."
|
||||
(if cvs-changelog-full-paragraphs
|
||||
(cvs-changelog-paragraph)
|
||||
(cvs-changelog-subparagraph)))
|
||||
|
||||
(defvar user-full-name)
|
||||
(defvar user-mail-address)
|
||||
(defun cvs-changelog-ours-p ()
|
||||
"See if ChangeLog entry at point is for the current user, today.
|
||||
Return non-nil iff it is."
|
||||
;; Code adapted from add-change-log-entry.
|
||||
(let ((name (or (and (boundp 'add-log-full-name) add-log-full-name)
|
||||
(and (fboundp 'user-full-name) (user-full-name))
|
||||
(and (boundp 'user-full-name) user-full-name)))
|
||||
(mail (or (and (boundp 'add-log-mailing-address) add-log-mailing-address)
|
||||
;;(and (fboundp 'user-mail-address) (user-mail-address))
|
||||
(and (boundp 'user-mail-address) user-mail-address)))
|
||||
(time (or (and (boundp 'add-log-time-format)
|
||||
(functionp add-log-time-format)
|
||||
(funcall add-log-time-format))
|
||||
(format-time-string "%Y-%m-%d"))))
|
||||
(looking-at (regexp-quote (format "%s %s <%s>" time name mail)))))
|
||||
|
||||
(defun cvs-changelog-entries (file)
|
||||
"Return the ChangeLog entries for FILE, and the ChangeLog they came from.
|
||||
The return value looks like this:
|
||||
(LOGBUFFER (ENTRYSTART . ENTRYEND) ...)
|
||||
where LOGBUFFER is the name of the ChangeLog buffer, and each
|
||||
\(ENTRYSTART . ENTRYEND\) pair is a buffer region."
|
||||
(save-excursion
|
||||
(let ((changelog-file-name
|
||||
(let ((default-directory
|
||||
(file-name-directory (expand-file-name file))))
|
||||
;; `find-change-log' uses `change-log-default-name' if set
|
||||
;; and sets it before exiting, so we need to work around
|
||||
;; that memoizing which is undesired here
|
||||
(setq change-log-default-name nil)
|
||||
(find-change-log))))
|
||||
(set-buffer (find-file-noselect changelog-file-name))
|
||||
(unless (eq major-mode 'change-log-mode) (change-log-mode))
|
||||
(goto-char (point-min))
|
||||
(if (looking-at "\\s-*\n") (goto-char (match-end 0)))
|
||||
(if (not (cvs-changelog-ours-p))
|
||||
(list (current-buffer))
|
||||
(save-restriction
|
||||
(cvs-narrow-changelog)
|
||||
(goto-char (point-min))
|
||||
|
||||
;; Search for the name of FILE relative to the ChangeLog. If that
|
||||
;; doesn't occur anywhere, they're not using full relative
|
||||
;; filenames in the ChangeLog, so just look for FILE; we'll accept
|
||||
;; some false positives.
|
||||
(let ((pattern (file-relative-name
|
||||
file (file-name-directory changelog-file-name))))
|
||||
(if (or (string= pattern "")
|
||||
(not (save-excursion
|
||||
(search-forward pattern nil t))))
|
||||
(setq pattern (file-name-nondirectory file)))
|
||||
|
||||
(let (texts)
|
||||
(while (search-forward pattern nil t)
|
||||
(let ((entry (cvs-changelog-entry)))
|
||||
(push entry texts)
|
||||
(goto-char (elt entry 1))))
|
||||
|
||||
(cons (current-buffer) texts))))))))
|
||||
|
||||
(defun cvs-changelog-insert-entries (buffer regions)
|
||||
"Insert those regions in BUFFER specified in REGIONS.
|
||||
Sort REGIONS front-to-back first."
|
||||
(let ((regions (sort regions 'car-less-than-car))
|
||||
(last))
|
||||
(dolist (region regions)
|
||||
(when (and last (< last (car region))) (newline))
|
||||
(setq last (elt region 1))
|
||||
(apply 'insert-buffer-substring buffer region))))
|
||||
|
||||
(defun cvs-insert-changelog-entries (files)
|
||||
"Given a list of files FILES, insert the ChangeLog entries for them."
|
||||
(let ((buffer-entries nil))
|
||||
|
||||
;; Add each buffer to buffer-entries, and associate it with the list
|
||||
;; of entries we want from that file.
|
||||
(dolist (file files)
|
||||
(let* ((entries (cvs-changelog-entries file))
|
||||
(pair (assq (car entries) buffer-entries)))
|
||||
(if pair
|
||||
(setcdr pair (cvs-union (cdr pair) (cdr entries)))
|
||||
(push entries buffer-entries))))
|
||||
|
||||
;; Now map over each buffer in buffer-entries, sort the entries for
|
||||
;; each buffer, and extract them as strings.
|
||||
(dolist (buffer-entry buffer-entries)
|
||||
(cvs-changelog-insert-entries (car buffer-entry) (cdr buffer-entry))
|
||||
(when (cdr buffer-entry) (newline)))))
|
||||
|
||||
(provide 'log-edit)
|
||||
;;; log-edit.el ends here
|
||||
189
lisp/log-view.el
Normal file
189
lisp/log-view.el
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
;;; log-view.el --- Major mode for browsing CVS log output
|
||||
|
||||
;; Copyright (C) 1999-2000 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Stefan Monnier <monnier@cs.yale.edu>
|
||||
;; Keywords: pcl-cvs cvs log
|
||||
;; Version: $Name: $
|
||||
;; Revision: $Id: log-view.el,v 1.2 2000/03/03 20:58:09 monnier Exp $
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs 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 2, or (at your option)
|
||||
;; any later version.
|
||||
|
||||
;; GNU Emacs 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 GNU Emacs; see the file COPYING. If not, write to the
|
||||
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
;; Boston, MA 02111-1307, USA.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; Todo:
|
||||
|
||||
;; - extract version info in log-view-current-tag
|
||||
;; - add support for SCCS' output format
|
||||
;; - add compatibility with cvs-log.el
|
||||
;; - add ability to modify a log-entry (via cvs-mode-admin ;-)
|
||||
|
||||
;;; Code:
|
||||
|
||||
(eval-when-compile (require 'cl))
|
||||
;;(require 'pcvs-defs)
|
||||
(require 'pcvs-util)
|
||||
|
||||
|
||||
(defgroup log-view nil
|
||||
"Major mode for browsing log output for PCL-CVS."
|
||||
:group 'pcl-cvs
|
||||
:prefix "log-view-")
|
||||
|
||||
(easy-mmode-defmap log-view-mode-map
|
||||
'(("n" . log-view-next-message)
|
||||
("N" . log-view-next-file)
|
||||
("M-n" . log-view-next-file)
|
||||
("p" . log-view-prev-message)
|
||||
("P" . log-view-prev-file)
|
||||
("M-p" . log-view-prev-file))
|
||||
"Log-View's keymap."
|
||||
:group 'log-view
|
||||
:inherit 'cvs-mode-map)
|
||||
|
||||
(defvar log-view-mode-hook nil
|
||||
"Hook run at the end of `log-view-mode'.")
|
||||
|
||||
(defface log-view-file-face
|
||||
'((((class color) (background light))
|
||||
(:background "grey70" :bold t))
|
||||
(t (:bold t)))
|
||||
"Face for the file header line in `log-view-mode'."
|
||||
:group 'log-view)
|
||||
(defvar log-view-file-face 'log-view-file-face)
|
||||
|
||||
(defface log-view-message-face
|
||||
'((((class color) (background light))
|
||||
(:background "grey85"))
|
||||
(t (:bold t)))
|
||||
"Face for the message header line in `log-view-mode'."
|
||||
:group 'log-view)
|
||||
(defvar log-view-message-face 'log-view-message-face)
|
||||
|
||||
(defconst log-view-file-re
|
||||
(concat "^\\("
|
||||
"Working file: \\(.+\\)"
|
||||
"\\|SCCS/s\\.\\(.+\\):"
|
||||
"\\)\n"))
|
||||
(defconst log-view-message-re "^----------------------------$")
|
||||
|
||||
(defconst log-view-font-lock-keywords
|
||||
`((,log-view-file-re
|
||||
(2 'cvs-filename-face nil t)
|
||||
(3 'cvs-filename-face nil t)
|
||||
(0 'log-view-file-face append))
|
||||
(,log-view-message-re . log-view-message-face)))
|
||||
(defconst log-view-font-lock-defaults
|
||||
'(log-view-font-lock-keywords t nil nil nil))
|
||||
|
||||
;;;;
|
||||
;;;; Actual code
|
||||
;;;;
|
||||
|
||||
;;;###autoload
|
||||
(autoload 'log-view-mode "log-view" "Major mode for browsing CVS log output." t)
|
||||
(eval-when-compile (autoload 'easy-mmode-define-derived-mode "easy-mmode"))
|
||||
(easy-mmode-define-derived-mode log-view-mode fundamental-mode "Log-View"
|
||||
"Major mode for browsing CVS log output."
|
||||
(set (make-local-variable 'font-lock-defaults) log-view-font-lock-defaults)
|
||||
(set (make-local-variable 'cvs-minor-wrap-function) 'log-view-minor-wrap))
|
||||
|
||||
;;;;
|
||||
;;;; Navigation
|
||||
;;;;
|
||||
|
||||
(defun log-view-next-message (&optional count)
|
||||
"Move to next (COUNT'th) log message."
|
||||
(interactive "p")
|
||||
(unless count (setq count 1))
|
||||
(if (< count 0) (log-view-prev-message (- count))
|
||||
(when (looking-at log-view-message-re) (incf count))
|
||||
(re-search-forward log-view-message-re nil nil count)
|
||||
(goto-char (match-beginning 0))))
|
||||
|
||||
(defun log-view-next-file (&optional count)
|
||||
"Move to next (COUNT'th) file."
|
||||
(interactive "p")
|
||||
(unless count (setq count 1))
|
||||
(if (< count 0) (log-view-prev-file (- count))
|
||||
(when (looking-at log-view-file-re) (incf count))
|
||||
(re-search-forward log-view-file-re nil nil count)
|
||||
(goto-char (match-beginning 0))))
|
||||
|
||||
(defun log-view-prev-message (&optional count)
|
||||
"Move to previous (COUNT'th) log message."
|
||||
(interactive "p")
|
||||
(unless count (setq count 1))
|
||||
(if (< count 0) (log-view-next-message (- count))
|
||||
(re-search-backward log-view-message-re nil nil count)))
|
||||
|
||||
(defun log-view-prev-file (&optional count)
|
||||
"Move to previous (COUNT'th) file."
|
||||
(interactive "p")
|
||||
(unless count (setq count 1))
|
||||
(if (< count 0) (log-view-next-file (- count))
|
||||
(re-search-backward log-view-file-re nil nil count)))
|
||||
|
||||
;;;;
|
||||
;;;; Linkage to PCL-CVS (mostly copied from cvs-status.el)
|
||||
;;;;
|
||||
|
||||
(defconst log-view-dir-re "^cvs[.ex]* [a-z]+: Logging \\(.+\\)$")
|
||||
|
||||
(defun log-view-current-file ()
|
||||
(save-excursion
|
||||
(forward-line 1)
|
||||
(or (re-search-backward log-view-file-re nil t)
|
||||
(re-search-forward log-view-file-re))
|
||||
(let* ((file (or (match-string 2) (match-string 3)))
|
||||
(cvsdir (and (re-search-backward log-view-dir-re nil t)
|
||||
(match-string 1)))
|
||||
(pcldir (and (re-search-backward cvs-pcl-cvs-dirchange-re nil t)
|
||||
(match-string 1)))
|
||||
(dir ""))
|
||||
(let ((default-directory ""))
|
||||
(when pcldir (setq dir (expand-file-name pcldir dir)))
|
||||
(when cvsdir (setq dir (expand-file-name cvsdir dir)))
|
||||
(expand-file-name file dir)))))
|
||||
|
||||
(defun log-view-current-tag ()
|
||||
nil);; FIXME
|
||||
|
||||
(defun log-view-minor-wrap (buf f)
|
||||
(let ((data (with-current-buffer buf
|
||||
(cons
|
||||
(cons (log-view-current-file)
|
||||
(log-view-current-tag))
|
||||
(when (ignore-errors (mark))
|
||||
;; `mark-active' is not provided by XEmacs :-(
|
||||
(save-excursion
|
||||
(goto-char (mark))
|
||||
(cons (log-view-current-file)
|
||||
(log-view-current-tag))))))))
|
||||
(let ((cvs-branch-prefix (cdar data))
|
||||
(cvs-secondary-branch-prefix (and (cdar data) (cddr data)))
|
||||
(cvs-minor-current-files
|
||||
(cons (caar data)
|
||||
(when (and (cadr data) (not (equal (caar data) (cadr data))))
|
||||
(list (cadr data)))))
|
||||
;; FIXME: I need to force because the fileinfos are UNKNOWN
|
||||
(cvs-force-command "/F"))
|
||||
(funcall f))))
|
||||
|
||||
(provide 'log-view)
|
||||
;;; log-view.el ends here
|
||||
501
lisp/pcvs-defs.el
Normal file
501
lisp/pcvs-defs.el
Normal file
|
|
@ -0,0 +1,501 @@
|
|||
;;; pcvs-defs.el --- variable definitions for PCL-CVS
|
||||
|
||||
;; Copyright (C) 1991-2000 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Stefan Monnier <monnier@cs.yale.edu>
|
||||
;; Keywords: pcl-cvs
|
||||
;; Version: $Name: $
|
||||
;; Revision: $Id: pcl-cvs-defs.el,v 1.27 2000/03/03 20:58:09 monnier Exp $
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs 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 2, or (at your option)
|
||||
;; any later version.
|
||||
|
||||
;; GNU Emacs 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 GNU Emacs; see the file COPYING. If not, write to the
|
||||
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
;; Boston, MA 02111-1307, USA.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
|
||||
;;; Code:
|
||||
|
||||
(defconst pcl-cvs-version "$Name: $")
|
||||
|
||||
(eval-when-compile (require 'cl))
|
||||
(require 'pcvs-util)
|
||||
|
||||
;;;; -------------------------------------------------------
|
||||
;;;; START OF THINGS TO CHECK WHEN INSTALLING
|
||||
|
||||
(defvar cvs-program "cvs"
|
||||
"*Name or full path of the cvs executable.")
|
||||
|
||||
(defvar cvs-version
|
||||
(ignore-errors
|
||||
(with-temp-buffer
|
||||
(call-process "cvs" nil t nil "-v")
|
||||
(goto-char (point-min))
|
||||
(when (re-search-forward "(CVS) \\([0-9]+\\)\\.\\([0-9]+\\)" nil t)
|
||||
(cons (string-to-number (match-string 1))
|
||||
(string-to-number (match-string 2))))))
|
||||
"*Version of `cvs' installed on your system.
|
||||
It must be in the (MAJOR . MINOR) format.")
|
||||
|
||||
;; FIXME: this is only used by cvs-mode-diff-backup
|
||||
(defvar cvs-diff-program (or (and (boundp 'diff-command) diff-command) "diff")
|
||||
"*Name or full path of the best diff program you've got.
|
||||
NOTE: there are some nasty bugs in the context diff variants of some vendor
|
||||
versions, such as the one in SunOS-4.")
|
||||
|
||||
;;;; END OF THINGS TO CHECK WHEN INSTALLING
|
||||
;;;; --------------------------------------------------------
|
||||
|
||||
;;;;
|
||||
;;;; User configuration variables:
|
||||
;;;;
|
||||
;;;; NOTE: these should be set in your ~/.emacs (or site-lisp/default.el) file.
|
||||
;;;;
|
||||
|
||||
(defgroup pcl-cvs nil
|
||||
"Special support for the CVS versioning system."
|
||||
:group 'tools
|
||||
:prefix "cvs-")
|
||||
|
||||
;;
|
||||
;; cvsrc options
|
||||
;;
|
||||
|
||||
(defcustom cvs-cvsrc-file "~/.cvsrc"
|
||||
"Path to your cvsrc file."
|
||||
:group 'pcl-cvs
|
||||
:type '(file))
|
||||
|
||||
(defvar cvs-shared-start 4
|
||||
"Index of the first shared flag.
|
||||
If set to 4, for instance, a numeric argument smaller than 4 will
|
||||
select a non-shared flag, while a numeric argument greater than 3
|
||||
will select a shared-flag.")
|
||||
|
||||
(defvar cvs-shared-flags (make-list cvs-shared-start nil)
|
||||
"List of flags whose settings is shared among several commands.")
|
||||
|
||||
(defvar cvs-cvsroot nil
|
||||
"*Specifies where the (current) cvs master repository is.
|
||||
Overrides the environment variable $CVSROOT by sending \" -d dir\" to
|
||||
all CVS commands. This switch is useful if you have multiple CVS
|
||||
repositories. It can be set interactively with \\[cvs-change-cvsroot.]
|
||||
There is no need to set this if $CVSROOT is set to a correct value.")
|
||||
|
||||
(defcustom cvs-auto-remove-handled nil
|
||||
"*If up-to-date files should be acknowledged automatically.
|
||||
If T, they will be removed from the *cvs* buffer after every command.
|
||||
If DELAYED, they will be removed from the *cvs* buffer before every command.
|
||||
If STATUS, they will only be removed after a `cvs-mode-status' command.
|
||||
Else, they will never be automatically removed from the *cvs* buffer."
|
||||
:group 'pcl-cvs
|
||||
:type '(choice (const nil) (const status) (const delayed) (const t)))
|
||||
|
||||
(defcustom cvs-auto-remove-directories 'handled
|
||||
"*If ALL, directory entries will never be shown.
|
||||
If HANLDED, only non-handled directories will be shown.
|
||||
If EMPTY, only non-empty directories will be shown."
|
||||
:group 'pcl-cvs
|
||||
:type '(choice (const :tag "No" nil) (const all) (const handled) (const empty)))
|
||||
|
||||
(defcustom cvs-auto-revert t
|
||||
"*Non-nil if changed files should automatically be reverted."
|
||||
:group 'pcl-cvs
|
||||
:type '(boolean))
|
||||
|
||||
(defcustom cvs-sort-ignore-file t
|
||||
"*Non-nil if `cvs-mode-ignore' should sort the .cvsignore automatically."
|
||||
:group 'pcl-cvs
|
||||
:type '(boolean))
|
||||
|
||||
(defcustom cvs-force-dir-tag t
|
||||
"*If non-nil, tagging can only be applied to directories.
|
||||
Tagging should generally be applied a directory at a time, but sometimes it is
|
||||
useful to be able to tag a single file. The normal way to do that is to use
|
||||
`cvs-mode-force-command' so as to temporarily override the restrictions,"
|
||||
:group 'pcl-cvs
|
||||
:type '(boolean))
|
||||
|
||||
(defcustom cvs-default-ignore-marks nil
|
||||
"*Non-nil if cvs mode commands should ignore any marked files.
|
||||
Normally they run on the files that are marked (with `cvs-mode-mark'),
|
||||
or the file under the cursor if no files are marked. If this variable
|
||||
is set to a non-nil value they will by default run on the file on the
|
||||
current line. See also `cvs-ignore-marks'"
|
||||
:group 'pcl-cvs
|
||||
:type '(boolean))
|
||||
|
||||
(defvar cvs-diff-ignore-marks t
|
||||
"Obsolete variable: use cvs-ignore-marks instead.")
|
||||
|
||||
(defcustom cvs-invert-ignore-marks
|
||||
(let ((l ()))
|
||||
(unless (equal cvs-diff-ignore-marks cvs-default-ignore-marks)
|
||||
(push "diff" l))
|
||||
(when (and cvs-force-dir-tag (not cvs-default-ignore-marks))
|
||||
(push "tag" l))
|
||||
l)
|
||||
"*List of cvs commands that invert the default ignore-mark behavior.
|
||||
Commands in this set will use the opposite default from the one set
|
||||
in `cvs-default-ignore-marks'."
|
||||
:group 'pcl-cvs
|
||||
:type '(set (const "diff")
|
||||
(const "tag")
|
||||
(const "ignore")))
|
||||
|
||||
(defcustom cvs-confirm-removals t
|
||||
"*Ask for confirmation before removing files.
|
||||
Non-nil means that PCL-CVS will ask confirmation before removing files
|
||||
except for files whose content can readily be recovered from the repository.
|
||||
A value of LIST means that the list of files to be deleted will be
|
||||
displayed when asking for confirmation."
|
||||
:group 'pcl-cvs
|
||||
:type '(choice (const list)
|
||||
(const t)
|
||||
(const nil)))
|
||||
|
||||
(defcustom cvs-add-default-message nil
|
||||
"*Default message to use when adding files.
|
||||
If set to NIL, `cvs-mode-add' will always prompt for a message."
|
||||
:group 'pcl-cvs
|
||||
:type '(choice (const :tag "Prompt" nil)
|
||||
(string)))
|
||||
|
||||
(defvar cvs-diff-buffer-name "*cvs-diff*"
|
||||
"Obsolete variable: use `cvs-buffer-name-alist' instead.")
|
||||
|
||||
(defcustom cvs-find-file-and-jump t
|
||||
"Jump to the modified area when finding a file.
|
||||
If non-nil, `cvs-mode-file-file' will place the cursor at the beginning of
|
||||
the modified area. If the file is not locally modified, this will obviously
|
||||
have no effect."
|
||||
:group 'pcl-cvs
|
||||
:type '(boolean))
|
||||
|
||||
(defcustom cvs-buffer-name-alist
|
||||
'(("diff" cvs-diff-buffer-name diff-mode)
|
||||
("status" "*cvs-info*" cvs-status-mode)
|
||||
("tree" (format "*cvs-%s*" cmd) cvs-status-mode)
|
||||
("message" "*cvs-commit*" nil log-edit)
|
||||
("log" "*cvs-info*" log-view-mode))
|
||||
"*Buffer name and mode to be used for each command.
|
||||
This is a list of elements of the form
|
||||
|
||||
(CMD BUFNAME MODE &optional POSTPROC)
|
||||
|
||||
CMD is the name of the command.
|
||||
BUFNAME is an expression that should evaluate to a string used as
|
||||
a buffer name. It can use the variable CMD if it wants to.
|
||||
MODE is the command to use to setup the buffer.
|
||||
POSTPROC is a function that should be executed when the command terminates
|
||||
|
||||
The CMD used for `cvs-mode-commit' is \"message\". For that special
|
||||
case, POSTPROC is called just after MODE with special arguments."
|
||||
:group 'pcl-cvs
|
||||
:type '(repeat
|
||||
(list (choice (const "diff")
|
||||
(const "status")
|
||||
(const "tree")
|
||||
(const "message")
|
||||
(const "log")
|
||||
(string))
|
||||
(choice (const "*vc-diff*")
|
||||
(const "*cvs-info*")
|
||||
(const "*cvs-commit*")
|
||||
(const (expand-file-name "*cvs-commit*"))
|
||||
(const (format "*cvs-%s*" cmd))
|
||||
(const (expand-file-name (format "*cvs-%s*" cmd)))
|
||||
(sexp :value "my-cvs-info-buffer")
|
||||
(const nil))
|
||||
(choice (function-item diff-mode)
|
||||
(function-item cvs-edit-mode)
|
||||
(function-item cvs-status-mode)
|
||||
function
|
||||
(const nil))
|
||||
(set :inline t
|
||||
(choice (function-item cvs-status-cvstrees)
|
||||
(function-item cvs-status-trees)
|
||||
function)))))
|
||||
|
||||
(defvar cvs-buffer-name '(expand-file-name "*cvs*" dir) ;; "*cvs*"
|
||||
"Name of the cvs buffer.
|
||||
This expression will be evaluated in an environment where DIR is set to
|
||||
the directory name of the cvs buffer.")
|
||||
|
||||
(defvar cvs-temp-buffer-name '(expand-file-name " *cvs-tmp*" dir)
|
||||
"*Name of the cvs temporary buffer.
|
||||
Output from cvs is placed here for asynchronous commands.")
|
||||
|
||||
(defcustom cvs-idiff-imerge-handlers
|
||||
(if (fboundp 'ediff)
|
||||
'(cvs-ediff-diff . cvs-ediff-merge)
|
||||
'(cvs-emerge-diff . cvs-emerge-merge))
|
||||
"*Pair of functions to be used for resp. diff'ing and merg'ing interactively."
|
||||
:group 'pcl-cvs
|
||||
:type '(choice (const :tag "Ediff" (cvs-ediff-diff . cvs-ediff-merge))
|
||||
(const :tag "Emerge" (cvs-emerge-diff . cvs-emerge-merge))))
|
||||
|
||||
(defvar pcl-cvs-load-hook nil
|
||||
"Run after loading pcl-cvs.")
|
||||
|
||||
(defvar cvs-mode-hook nil
|
||||
"Run after `cvs-mode' was setup.")
|
||||
|
||||
|
||||
;;;;
|
||||
;;;; Internal variables, used in the process buffer.
|
||||
;;;;
|
||||
|
||||
(defvar cvs-postprocess nil
|
||||
"(Buffer local) what to do once the process exits.")
|
||||
|
||||
;;;;
|
||||
;;;; Internal variables for the *cvs* buffer.
|
||||
;;;;
|
||||
|
||||
(defcustom cvs-reuse-cvs-buffer 'subdir
|
||||
"When to reuse an existing cvs buffer.
|
||||
Alternatives are:
|
||||
CURRENT: just reuse the current buffer if it is a cvs buffer
|
||||
SAMEDIR: reuse any cvs buffer displaying the same directory
|
||||
SUBDIR: or reuse any cvs buffer displaying any sub- or super- directory
|
||||
ALWAYS: reuse any cvs buffer."
|
||||
:group 'pcl-cvs
|
||||
:type '(choice (const always) (const subdir) (const samedir) (const current)))
|
||||
|
||||
(defvar cvs-temp-buffer nil
|
||||
"(Buffer local) The temporary buffer associated with this *cvs* buffer.")
|
||||
|
||||
(defvar cvs-lock-file nil
|
||||
"Full path to a lock file that CVS is waiting for (or was waiting for).
|
||||
This variable is buffer local and only used in the *cvs* buffer.")
|
||||
|
||||
(defvar cvs-lock-file-regexp "^#cvs\\.\\([trw]fl\\.[-.a-z0-9]+\\|lock\\)\\'"
|
||||
"Regexp matching the possible names of locks in the CVS repository.")
|
||||
|
||||
(defconst cvs-cursor-column 22
|
||||
"Column to position cursor in in `cvs-mode'.")
|
||||
|
||||
;;;;
|
||||
;;;; Global internal variables
|
||||
;;;;
|
||||
|
||||
(defconst cvs-startup-message
|
||||
(concat "PCL-CVS release " pcl-cvs-version)
|
||||
"*Startup message for CVS.")
|
||||
|
||||
(defconst cvs-vendor-branch "1.1.1"
|
||||
"The default branch used by CVS for vendor code.")
|
||||
|
||||
(defvar cvs-menu
|
||||
'("CVS"
|
||||
["Open File.." cvs-mode-find-file t]
|
||||
[" ..Other Window" cvs-mode-find-file-other-window t]
|
||||
["Interactive Merge" cvs-mode-imerge t]
|
||||
["Interactive Diff" cvs-mode-idiff t]
|
||||
["View Diff" cvs-mode-diff (cvs-enabledp 'diff)]
|
||||
["Diff with Vendor" cvs-mode-diff-vendor t]
|
||||
["Diff with Backup" cvs-mode-diff-backup t]
|
||||
["View Log" cvs-mode-log t]
|
||||
["View Status" cvs-mode-status t]
|
||||
"----"
|
||||
["Update" cvs-mode-update (cvs-enabledp 'update)]
|
||||
["Re-Examine" cvs-mode-examine t]
|
||||
["Commit" cvs-mode-commit-setup (cvs-enabledp 'commit)]
|
||||
["Undo Changes" cvs-mode-undo (cvs-enabledp 'undo)]
|
||||
["Add" cvs-mode-add (cvs-enabledp 'add)]
|
||||
["Remove" cvs-mode-remove (cvs-enabledp 'remove)]
|
||||
["Ignore" cvs-mode-ignore (cvs-enabledp 'ignore)]
|
||||
["Add ChangeLog" cvs-mode-add-change-log-entry-other-window t]
|
||||
"----"
|
||||
["Mark All" cvs-mode-mark-all-files t]
|
||||
["Unmark All" cvs-mode-unmark-all-files t]
|
||||
["Hide Handled" cvs-mode-remove-handled t]
|
||||
"----"
|
||||
;; ["Update Directory" cvs-update t]
|
||||
;; ["Examine Directory" cvs-examine t]
|
||||
;; ["Status Directory" cvs-status t]
|
||||
;; ["Checkout Module" cvs-checkout t]
|
||||
;; "----"
|
||||
["Quit" cvs-mode-quit t]
|
||||
))
|
||||
|
||||
(easy-mmode-defmap cvs-mode-diff-map
|
||||
'(("=" . cvs-mode-diff)
|
||||
("b" . cvs-mode-diff-backup)
|
||||
("2" . cvs-mode-idiff-other)
|
||||
("h" . cvs-mode-diff-head)
|
||||
("v" . cvs-mode-diff-vendor)
|
||||
("?" . cvs-mode-diff-help)
|
||||
("e" . cvs-mode-idiff)
|
||||
("E" . cvs-mode-imerge))
|
||||
"Keymap for diff-related operations in `cvs-mode'.")
|
||||
|
||||
(easy-mmode-defmap cvs-mode-map
|
||||
;;(define-prefix-command 'cvs-mode-map-diff-prefix)
|
||||
;;(define-prefix-command 'cvs-mode-map-control-c-prefix)
|
||||
`(;; simulate `suppress-keymap'
|
||||
(self-insert-command . undefined)
|
||||
(("0" "1" "2" "3" "4" "5" "6" "7" "8" "9") . digit-argument)
|
||||
("-" . negative-argument)
|
||||
;; various
|
||||
(undo . cvs-mode-undo)
|
||||
("?" . cvs-help)
|
||||
("h" . cvs-help)
|
||||
("q" . cvs-bury-buffer)
|
||||
;;("Q" . kill-buffer)
|
||||
("F" . cvs-mode-set-flags)
|
||||
("\M-f" . cvs-mode-force-command)
|
||||
("\C-c\C-c" . cvs-mode-kill-process)
|
||||
;; marking
|
||||
("m" . cvs-mode-mark)
|
||||
("M" . cvs-mode-mark-all-files)
|
||||
("u" . cvs-mode-unmark)
|
||||
("\C-?". cvs-mode-unmark-up)
|
||||
("%" . cvs-mode-mark-matching-files)
|
||||
("T" . cvs-mode-toggle-marks)
|
||||
("\M-\C-?" . cvs-mode-unmark-all-files)
|
||||
;; navigation keys
|
||||
(" " . cvs-mode-next-line)
|
||||
("n" . cvs-mode-next-line)
|
||||
("p" . cvs-mode-previous-line)
|
||||
;; M- keys are usually those that operate on modules
|
||||
;;("\M-C". cvs-mode-rcs2log) ; i.e. "Create a ChangeLog"
|
||||
;;("\M-t". cvs-rtag)
|
||||
;;("\M-l". cvs-rlog)
|
||||
("\M-c". cvs-checkout)
|
||||
("\M-e". cvs-examine)
|
||||
("g" . cvs-mode-revert-buffer)
|
||||
("\M-u". cvs-update)
|
||||
("\M-s". cvs-status)
|
||||
;; diff commands
|
||||
("=" . cvs-mode-diff)
|
||||
("d" . ,cvs-mode-diff-map)
|
||||
;; keys that operate on individual files
|
||||
("\C-k". cvs-mode-acknowledge)
|
||||
("A" . cvs-mode-add-change-log-entry-other-window)
|
||||
;;("B" . cvs-mode-byte-compile-files)
|
||||
("C" . cvs-mode-commit-setup)
|
||||
("O" . cvs-mode-update)
|
||||
("U" . cvs-mode-undo)
|
||||
("I" . cvs-mode-insert)
|
||||
("a" . cvs-mode-add)
|
||||
("b" . cvs-set-branch-prefix)
|
||||
("B" . cvs-set-secondary-branch-prefix)
|
||||
("c" . cvs-mode-commit)
|
||||
("e" . cvs-mode-examine)
|
||||
("f" . cvs-mode-find-file)
|
||||
("i" . cvs-mode-ignore)
|
||||
("l" . cvs-mode-log)
|
||||
("o" . cvs-mode-find-file-other-window)
|
||||
("r" . cvs-mode-remove)
|
||||
("s" . cvs-mode-status)
|
||||
("t" . cvs-mode-tag)
|
||||
;;("v" . cvs-mode-diff-vendor)
|
||||
("x" . cvs-mode-remove-handled)
|
||||
;; cvstree bindings
|
||||
("+" . cvs-mode-tree)
|
||||
;; mouse bindings
|
||||
([(down-mouse-3)] . cvs-menu)
|
||||
;; Emacs-21 toolbar
|
||||
;;([tool-bar item1] . (menu-item "Examine" cvs-examine :image (image :file "/usr/share/icons/xpaint.xpm" :type xpm)))
|
||||
;;([tool-bar item2] . (menu-item "Update" cvs-update :image (image :file "/usr/share/icons/mail1.xpm" :type xpm)))
|
||||
)
|
||||
"Keymap for `cvs-mode'."
|
||||
:dense t)
|
||||
|
||||
(fset 'cvs-mode-map cvs-mode-map)
|
||||
|
||||
;; add the cvs-menu to the map so it's added whenever we are in cvs-mode
|
||||
(when (ignore-errors (require 'easymenu))
|
||||
(easy-menu-define cvs-menu-map
|
||||
cvs-mode-map
|
||||
"Menu used in cvs-mode."
|
||||
cvs-menu))
|
||||
|
||||
;;;;
|
||||
;;;; CVS-Minor mode
|
||||
;;;;
|
||||
|
||||
(defcustom cvs-minor-mode-prefix "\C-xc"
|
||||
"Prefix key for the `cvs-mode' bindings in `cvs-minor-mode'."
|
||||
:group 'pcl-cvs)
|
||||
|
||||
(easy-mmode-defmap cvs-minor-mode-map
|
||||
`((,cvs-minor-mode-prefix . cvs-mode-map))
|
||||
"Keymap for `cvs-minor-mode', used in buffers related to pcl-cvs.")
|
||||
|
||||
(defvar cvs-buffer nil
|
||||
"(Buffer local) The *cvs* buffer associated with this buffer.")
|
||||
(put 'cvs-buffer 'permanent-local t)
|
||||
;;(make-variable-buffer-local 'cvs-buffer)
|
||||
|
||||
(defvar cvs-minor-wrap-function nil
|
||||
"Function to call when switching to the *cvs* buffer.
|
||||
Takes two arguments:
|
||||
- a *cvs* buffer.
|
||||
- a zero-arg function which is guaranteed not to switch buffer.
|
||||
It is expected to call the function.")
|
||||
;;(make-variable-buffer-local 'cvs-minor-wrap-function)
|
||||
|
||||
(defvar cvs-minor-current-files)
|
||||
;;"Current files in a `cvs-minor-mode' buffer."
|
||||
;; This should stay `void' because we want to be able to tell the difference
|
||||
;; between an empty list and no list at all.
|
||||
|
||||
(defconst cvs-pcl-cvs-dirchange-re "^pcl-cvs: descending directory \\(.*\\)$")
|
||||
|
||||
;;;;
|
||||
;;;;
|
||||
;;;;
|
||||
|
||||
;;;###autoload
|
||||
(if (progn (condition-case () (require 'easymenu) (error nil))
|
||||
(fboundp 'easy-menu-add-item))
|
||||
(easy-menu-add-item nil '("tools")
|
||||
'("PCL CVS"
|
||||
["Update Directory" cvs-update t]
|
||||
["Examine Directory" cvs-examine t]
|
||||
["Status Directory" cvs-status t]
|
||||
["Checkout Module" cvs-checkout t]) "vc"))
|
||||
|
||||
|
||||
;; cvs-1.10 and above can take file arguments in other directories
|
||||
;; while others need to be executed once per directory
|
||||
(defvar cvs-execute-single-dir
|
||||
(if (and (consp cvs-version)
|
||||
(or (>= (cdr cvs-version) 10) (> (car cvs-version) 1)))
|
||||
'("status")
|
||||
t)
|
||||
"Whether cvs commands should be executed a directory at a time.
|
||||
If a list, specifies for which commands the single-dir mode should be used.
|
||||
If T, single-dir mode should be used for all operations.
|
||||
|
||||
CVS versions before 1.10 did not allow passing them arguments in different
|
||||
directories, so pcl-cvs checks what version you're using to determine
|
||||
whether to use the new feature or not.
|
||||
Sadly, even with a new cvs executable, if you connect to an older cvs server
|
||||
\(typically a cvs-1.9 on the server), the old restriction applies. In such
|
||||
a case the sanity check made by pcl-cvs fails and you will have to manually
|
||||
set this variable to T (until the cvs server is upgraded).
|
||||
When the above problem occurs, pcl-cvs should (hopefully) catch cvs' error
|
||||
message and replace it with a message tell you to change this variable.")
|
||||
|
||||
;;
|
||||
(provide 'pcvs-defs)
|
||||
|
||||
;;; pcl-cvs-defs.el ends here
|
||||
455
lisp/pcvs-info.el
Normal file
455
lisp/pcvs-info.el
Normal file
|
|
@ -0,0 +1,455 @@
|
|||
;;; pcvs-info.el --- Internal representation of a fileinfo entry
|
||||
|
||||
;; Copyright (C) 1991-2000 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Stefan Monnier <monnier@cs.yale.edu>
|
||||
;; Keywords: pcl-cvs
|
||||
;; Version: $Name: $
|
||||
;; Revision: $Id: pcl-cvs-info.el,v 1.28 2000/03/05 21:32:21 monnier Exp $
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs 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 2, or (at your option)
|
||||
;; any later version.
|
||||
|
||||
;; GNU Emacs 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 GNU Emacs; see the file COPYING. If not, write to the
|
||||
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
;; Boston, MA 02111-1307, USA.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; The cvs-fileinfo data structure:
|
||||
;;
|
||||
;; When the `cvs update' is ready we parse the output. Every file
|
||||
;; that is affected in some way is added to the cookie collection as
|
||||
;; a "fileinfo" (as defined below in cvs-create-fileinfo).
|
||||
|
||||
;;; Code:
|
||||
|
||||
(eval-when-compile (require 'cl))
|
||||
(require 'pcvs-util)
|
||||
;;(require 'pcvs-defs)
|
||||
|
||||
;;;;
|
||||
;;;; config variables
|
||||
;;;;
|
||||
|
||||
(defcustom cvs-display-full-path t
|
||||
"*Specifies how the filenames should look like in the listing.
|
||||
If t, their full path name will be displayed, else only the filename."
|
||||
:group 'pcl-cvs
|
||||
:type '(boolean))
|
||||
|
||||
(defvar global-font-lock-mode)
|
||||
(defvar font-lock-auto-fontify)
|
||||
(defcustom cvs-highlight
|
||||
(or (and (boundp 'font-lock-auto-fontify) font-lock-auto-fontify)
|
||||
(and (boundp 'global-font-lock-mode) global-font-lock-mode))
|
||||
"*Whether to use text highlighting (à la font-lock) or not."
|
||||
:group 'pcl-cvs
|
||||
:type '(boolean))
|
||||
|
||||
(defcustom cvs-allow-dir-commit nil
|
||||
"*Allow `cvs-mode-commit' on directories.
|
||||
If you commit without any marked file and with the cursor positioned
|
||||
on a directory entry, cvs would commit the whole directory. This seems
|
||||
to confuse some users sometimes."
|
||||
:group 'pcl-cvs
|
||||
:type '(boolean))
|
||||
|
||||
|
||||
;;;;
|
||||
;;;; Faces for fontification
|
||||
;;;;
|
||||
|
||||
(defface cvs-header-face
|
||||
'((((class color) (background dark))
|
||||
(:foreground "lightyellow" :bold t))
|
||||
(((class color) (background light))
|
||||
(:foreground "blue4" :bold t))
|
||||
(t (:bold t)))
|
||||
"PCL-CVS face used to highlight directory changes."
|
||||
:group 'pcl-cvs)
|
||||
|
||||
(defface cvs-filename-face
|
||||
'((((class color) (background dark))
|
||||
(:foreground "lightblue"))
|
||||
(((class color) (background light))
|
||||
(:foreground "blue4"))
|
||||
(t ()))
|
||||
"PCL-CVS face used to highlight file names."
|
||||
:group 'pcl-cvs)
|
||||
|
||||
(defface cvs-unknown-face
|
||||
'((((class color) (background dark))
|
||||
(:foreground "red"))
|
||||
(((class color) (background light))
|
||||
(:foreground "red"))
|
||||
(t (:italic t)))
|
||||
"PCL-CVS face used to highlight unknown file status."
|
||||
:group 'pcl-cvs)
|
||||
|
||||
(defface cvs-handled-face
|
||||
'((((class color) (background dark))
|
||||
(:foreground "pink"))
|
||||
(((class color) (background light))
|
||||
(:foreground "pink"))
|
||||
(t ()))
|
||||
"PCL-CVS face used to highlight handled file status."
|
||||
:group 'pcl-cvs)
|
||||
|
||||
(defface cvs-need-action-face
|
||||
'((((class color) (background dark))
|
||||
(:foreground "orange"))
|
||||
(((class color) (background light))
|
||||
(:foreground "orange"))
|
||||
(t (:italic t)))
|
||||
"PCL-CVS face used to highlight status of files needing action."
|
||||
:group 'pcl-cvs)
|
||||
|
||||
(defface cvs-marked-face
|
||||
'((((class color) (background dark))
|
||||
(:foreground "green" :bold t))
|
||||
(((class color) (background light))
|
||||
(:foreground "green3" :bold t))
|
||||
(t (:bold t)))
|
||||
"PCL-CVS face used to highlight marked file indicator."
|
||||
:group 'pcl-cvs)
|
||||
|
||||
(defface cvs-msg-face
|
||||
'((t (:italic t)))
|
||||
"PCL-CVS face used to highlight CVS messages."
|
||||
:group 'pcl-cvs)
|
||||
|
||||
|
||||
;; There is normally no need to alter the following variable, but if
|
||||
;; your site has installed CVS in a non-standard way you might have
|
||||
;; to change it.
|
||||
|
||||
(defvar cvs-bakprefix ".#"
|
||||
"The prefix that CVS prepends to files when rcsmerge'ing.")
|
||||
|
||||
(easy-mmode-defmap cvs-filename-map
|
||||
'(([(mouse-2)] . cvs-mode-find-file))
|
||||
"Local keymap for text properties of file names"
|
||||
:inherit 'cvs-mode-map)
|
||||
|
||||
(easy-mmode-defmap cvs-status-map
|
||||
'(([(mouse-2)] . cvs-mouse-toggle-mark))
|
||||
"Local keymap for text properties of status"
|
||||
:inherit 'cvs-mode-map)
|
||||
|
||||
(easy-mmode-defmap cvs-dirname-map
|
||||
'(([(mouse-2)] . cvs-mode-find-file))
|
||||
"Local keymap for text properties of directory names"
|
||||
:inherit 'cvs-mode-map)
|
||||
|
||||
;; Constructor:
|
||||
|
||||
(defstruct (cvs-fileinfo
|
||||
(:constructor nil)
|
||||
(:copier nil)
|
||||
(:constructor -cvs-create-fileinfo (type dir file full-log
|
||||
&key marked subtype
|
||||
merge
|
||||
base-rev
|
||||
head-rev))
|
||||
(:conc-name cvs-fileinfo->))
|
||||
marked ;; t/nil.
|
||||
type ;; See below
|
||||
subtype ;; See below
|
||||
dir ;; Relative directory the file resides in.
|
||||
;; (concat dir file) should give a valid path.
|
||||
file ;; The file name sans the directory.
|
||||
base-rev ;; During status: This is the revision that the
|
||||
;; working file is based on.
|
||||
head-rev ;; During status: This is the highest revision in
|
||||
;; the repository.
|
||||
merge ;; A cons cell containing the (ancestor . head) revisions
|
||||
;; of the merge that resulted in the current file.
|
||||
;;removed ;; t if the file no longer exists.
|
||||
full-log ;; The output from cvs, unparsed.
|
||||
;;mod-time ;; Not used.
|
||||
|
||||
;; In addition to the above, the following values can be extracted:
|
||||
|
||||
;; handled ;; t if this file doesn't require further action.
|
||||
;; full-path ;; The complete relative filename.
|
||||
;; pp-name ;; The printed file name
|
||||
;; backup-file;; For MERGED and CONFLICT files after a \"cvs update\",
|
||||
;; this is a full path to the backup file where the
|
||||
;; untouched version resides.
|
||||
|
||||
;; The meaning of the type field:
|
||||
|
||||
;; Value ---Used by--- Explanation
|
||||
;; update status
|
||||
;; NEED-UPDATE x file needs update
|
||||
;; MODIFIED x x modified by you, unchanged in repository
|
||||
;; MERGED x x successful merge
|
||||
;; ADDED x x added by you, not yet committed
|
||||
;; MISSING x rm'd, but not yet `cvs remove'd
|
||||
;; REMOVED x x removed by you, not yet committed
|
||||
;; NEED-MERGE x need merge
|
||||
;; CONFLICT x conflict when merging
|
||||
;; ;;MOD-CONFLICT x removed locally, changed in repository.
|
||||
;; DIRCHANGE x x A change of directory.
|
||||
;; UNKNOWN x An unknown file.
|
||||
;; UP-TO-DATE x The file is up-to-date.
|
||||
;; UPDATED x x file copied from repository
|
||||
;; PATCHED x x diff applied from repository
|
||||
;; COMMITTED x x cvs commit'd
|
||||
;; DEAD An entry that should be removed
|
||||
;; MESSAGE x x This is a special fileinfo that is used
|
||||
;; to display a text that should be in
|
||||
;; full-log."
|
||||
;; TEMP A temporary message that should be removed
|
||||
;; HEADER A message that should stick at the top of the display
|
||||
;; FOOTER A message that should stick at the bottom of the display
|
||||
)
|
||||
(defun cvs-create-fileinfo (type dir file msg &rest keys)
|
||||
(cvs-check-fileinfo (apply #'-cvs-create-fileinfo type dir file msg keys)))
|
||||
|
||||
;; Fake selectors:
|
||||
|
||||
(defun cvs-fileinfo->full-path (fileinfo)
|
||||
"Return the full path for the file that is described in FILEINFO."
|
||||
(let ((dir (cvs-fileinfo->dir fileinfo)))
|
||||
(if (eq (cvs-fileinfo->type fileinfo) 'DIRCHANGE)
|
||||
(if (string= dir "") "." (directory-file-name dir))
|
||||
;; Here, I use `concat' rather than `expand-file-name' because I want
|
||||
;; the resulting path to stay relative if `dir' is relative.
|
||||
;; I could also use `expand-file-name' with `default-directory = ""'
|
||||
(concat dir (cvs-fileinfo->file fileinfo)))))
|
||||
|
||||
(defun cvs-fileinfo->pp-name (fi)
|
||||
"Return the filename of FI as it should be displayed."
|
||||
(if cvs-display-full-path
|
||||
(cvs-fileinfo->full-path fi)
|
||||
(cvs-fileinfo->file fi)))
|
||||
|
||||
(defun cvs-fileinfo->backup-file (fileinfo)
|
||||
"Construct the file name of the backup file for FILEINFO."
|
||||
(let* ((dir (cvs-fileinfo->dir fileinfo))
|
||||
(file (cvs-fileinfo->file fileinfo))
|
||||
(default-directory (file-name-as-directory (expand-file-name dir)))
|
||||
(files (directory-files "." nil
|
||||
(concat "^" (regexp-quote cvs-bakprefix)
|
||||
(regexp-quote file) "\\.")))
|
||||
bf)
|
||||
(dolist (f files bf)
|
||||
(when (and (file-readable-p f)
|
||||
(or (null bf) (file-newer-than-file-p f bf)))
|
||||
(setq bf (concat dir f))))))
|
||||
|
||||
;; (defun cvs-fileinfo->handled (fileinfo)
|
||||
;; "Tell if this requires further action"
|
||||
;; (memq (cvs-fileinfo->type fileinfo) '(UP-TO-DATE DEAD)))
|
||||
|
||||
|
||||
;; Predicate:
|
||||
|
||||
(defun boolp (x) (or (eq t x) (null x)))
|
||||
(defun cvs-check-fileinfo (fi)
|
||||
"Check FI's conformance to some conventions."
|
||||
(let ((check 'none)
|
||||
(type (cvs-fileinfo->type fi))
|
||||
(subtype (cvs-fileinfo->subtype fi))
|
||||
(marked (cvs-fileinfo->marked fi))
|
||||
(dir (cvs-fileinfo->dir fi))
|
||||
(file (cvs-fileinfo->file fi))
|
||||
(base-rev (cvs-fileinfo->base-rev fi))
|
||||
(head-rev (cvs-fileinfo->head-rev fi))
|
||||
(full-log (cvs-fileinfo->full-log fi)))
|
||||
(if (and (setq check 'marked) (boolp marked)
|
||||
(setq check 'base-rev) (or (null base-rev) (stringp base-rev))
|
||||
(setq check 'head-rev) (or (null head-rev) (stringp head-rev))
|
||||
(setq check 'full-log) (stringp full-log)
|
||||
(setq check 'dir)
|
||||
(and (stringp dir)
|
||||
(not (file-name-absolute-p dir))
|
||||
(or (string= dir "")
|
||||
(string= dir (file-name-as-directory dir))))
|
||||
(setq check 'file)
|
||||
(and (stringp file)
|
||||
(string= file (file-name-nondirectory file)))
|
||||
(setq check 'type) (symbolp type)
|
||||
(setq check 'consistency)
|
||||
(case type
|
||||
(DIRCHANGE (and (null subtype) (string= "." file)))
|
||||
((NEED-UPDATE ADDED MISSING DEAD MODIFIED MESSAGE UP-TO-DATE
|
||||
REMOVED NEED-MERGE CONFLICT UNKNOWN MESSAGE)
|
||||
t)))
|
||||
fi
|
||||
(error "Invalid :%s in cvs-fileinfo %s" check fi))))
|
||||
|
||||
|
||||
;;;;
|
||||
;;;; State table to indicate what you can do when.
|
||||
;;;;
|
||||
|
||||
(defconst cvs-states
|
||||
`((NEED-UPDATE update diff)
|
||||
(UP-TO-DATE update nil remove diff safe-rm revert)
|
||||
(MODIFIED update commit undo remove diff merge diff-base)
|
||||
(ADDED update commit remove)
|
||||
(MISSING remove undo update safe-rm revert)
|
||||
(REMOVED commit add undo safe-rm)
|
||||
(NEED-MERGE update undo diff diff-base)
|
||||
(CONFLICT merge remove undo commit diff diff-base)
|
||||
(DIRCHANGE remove update diff ,(if cvs-allow-dir-commit 'commit) tag)
|
||||
(UNKNOWN ignore add remove)
|
||||
(DEAD )
|
||||
(MESSAGE))
|
||||
"Fileinfo state descriptions for pcl-cvs.
|
||||
This is an assoc list. Each element consists of (STATE . FUNS)
|
||||
- STATE (described in `cvs-create-fileinfo') is the key
|
||||
- FUNS is the list of applicable operations.
|
||||
The first one (if any) should be the \"default\" action.
|
||||
Most of the actions have the obvious meaning.
|
||||
`safe-rm' indicates that the file can be removed without losing
|
||||
any information.")
|
||||
|
||||
;;;;
|
||||
;;;; Utility functions
|
||||
;;;;
|
||||
|
||||
;;----------
|
||||
(defun cvs-applicable-p (fi-or-type func)
|
||||
"Check if FUNC is applicable to FI-OR-TYPE.
|
||||
If FUNC is nil, always return t.
|
||||
FI-OR-TYPE can either be a symbol (a fileinfo-type) or a fileinfo."
|
||||
(let ((type (if (symbolp fi-or-type) fi-or-type
|
||||
(cvs-fileinfo->type fi-or-type))))
|
||||
(and (not (eq type 'MESSAGE))
|
||||
(eq (car (memq func (cdr (assq type cvs-states)))) func))))
|
||||
|
||||
;; (defun cvs-default-action (fileinfo)
|
||||
;; "Return some kind of \"default\" action to be performed."
|
||||
;; (second (assq (cvs-fileinfo->type fileinfo) cvs-states)))
|
||||
|
||||
;; fileinfo pretty-printers:
|
||||
|
||||
(defun cvs-add-face (str face &optional keymap)
|
||||
(when cvs-highlight
|
||||
(add-text-properties 0 (length str)
|
||||
(list* 'face face
|
||||
(when keymap
|
||||
(list 'mouse-face 'highlight
|
||||
'local-map keymap)))
|
||||
str))
|
||||
str)
|
||||
|
||||
;;----------
|
||||
(defun cvs-fileinfo-pp (fileinfo)
|
||||
"Pretty print FILEINFO. Insert a printed representation in current buffer.
|
||||
For use by the cookie package."
|
||||
(cvs-check-fileinfo fileinfo)
|
||||
(let ((type (cvs-fileinfo->type fileinfo))
|
||||
(subtype (cvs-fileinfo->subtype fileinfo)))
|
||||
(insert
|
||||
(case type
|
||||
(DIRCHANGE (concat "In directory "
|
||||
(cvs-add-face (cvs-fileinfo->full-path fileinfo)
|
||||
'cvs-header-face cvs-dirname-map)
|
||||
":"))
|
||||
(MESSAGE
|
||||
(if (memq (cvs-fileinfo->subtype fileinfo) '(FOOTER HEADER))
|
||||
(cvs-fileinfo->full-log fileinfo)
|
||||
(cvs-add-face (format "Message: %s" (cvs-fileinfo->full-log fileinfo))
|
||||
'cvs-msg-face)))
|
||||
(t
|
||||
(let* ((status (if (cvs-fileinfo->marked fileinfo)
|
||||
(cvs-add-face "*" 'cvs-marked-face)
|
||||
" "))
|
||||
(file (cvs-add-face (cvs-fileinfo->pp-name fileinfo)
|
||||
'cvs-filename-face cvs-filename-map))
|
||||
(base (or (cvs-fileinfo->base-rev fileinfo) ""))
|
||||
(head (cvs-fileinfo->head-rev fileinfo))
|
||||
(type
|
||||
(let ((str (case type
|
||||
;;(MOD-CONFLICT "Not Removed")
|
||||
(DEAD "")
|
||||
(t (capitalize (symbol-name type)))))
|
||||
(face (case type
|
||||
(UP-TO-DATE 'cvs-handled-face)
|
||||
(UNKNOWN 'cvs-unknown-face)
|
||||
(t 'cvs-need-action-face))))
|
||||
(cvs-add-face str face cvs-status-map)))
|
||||
(side (or
|
||||
;; maybe a subtype
|
||||
(when subtype (downcase (symbol-name subtype)))
|
||||
;; or the head-rev
|
||||
(when (and head (not (string= head base))) head)
|
||||
;; or nothing
|
||||
""))
|
||||
;; (action (cvs-add-face (case (cvs-default-action fileinfo)
|
||||
;; (commit "com")
|
||||
;; (update "upd")
|
||||
;; (undo "udo")
|
||||
;; (t " "))
|
||||
;; 'cvs-action-face
|
||||
;; cvs-action-map))
|
||||
)
|
||||
(concat (cvs-string-fill side 11) " "
|
||||
status " "
|
||||
(cvs-string-fill type 11) " "
|
||||
;; action " "
|
||||
(cvs-string-fill base 11) " "
|
||||
file)))))))
|
||||
;; it seems that `format' removes text-properties. Too bad!
|
||||
;; (format "%-11s %s %-11s %-11s %s"
|
||||
;; side status type base file)))))))
|
||||
|
||||
|
||||
(defun cvs-fileinfo-update (fi fi-new)
|
||||
"Update FI with the information provided in FI-NEW."
|
||||
(let ((type (cvs-fileinfo->type fi-new))
|
||||
(merge (cvs-fileinfo->merge fi-new)))
|
||||
(setf (cvs-fileinfo->type fi) type)
|
||||
(setf (cvs-fileinfo->subtype fi) (cvs-fileinfo->subtype fi-new))
|
||||
(setf (cvs-fileinfo->full-log fi) (cvs-fileinfo->full-log fi-new))
|
||||
(setf (cvs-fileinfo->base-rev fi) (cvs-fileinfo->base-rev fi-new))
|
||||
(setf (cvs-fileinfo->head-rev fi) (cvs-fileinfo->head-rev fi-new))
|
||||
(cond
|
||||
(merge (setf (cvs-fileinfo->merge fi) merge))
|
||||
((memq type '(UP-TO-DATE NEED-UPDATE))
|
||||
(setf (cvs-fileinfo->merge fi) nil)))))
|
||||
|
||||
;;----------
|
||||
(defun cvs-fileinfo< (a b)
|
||||
"Compare fileinfo A with fileinfo B and return t if A is `less'.
|
||||
The ordering defined by this function is such that directories are
|
||||
sorted alphabetically, and inside every directory the DIRCHANGE
|
||||
fileinfo will appear first, followed by all files (alphabetically)."
|
||||
(let ((subtypea (cvs-fileinfo->subtype a))
|
||||
(subtypeb (cvs-fileinfo->subtype b)))
|
||||
(cond
|
||||
;; keep header and footer where they belong. Note: the order is important
|
||||
((eq subtypeb 'HEADER) nil)
|
||||
((eq subtypea 'HEADER) t)
|
||||
((eq subtypea 'FOOTER) nil)
|
||||
((eq subtypeb 'FOOTER) t)
|
||||
|
||||
;; Sort according to directories.
|
||||
((string< (cvs-fileinfo->dir a) (cvs-fileinfo->dir b)) t)
|
||||
((not (string= (cvs-fileinfo->dir a) (cvs-fileinfo->dir b))) nil)
|
||||
|
||||
;; The DIRCHANGE entry is always first within the directory.
|
||||
((eq (cvs-fileinfo->type b) 'DIRCHANGE) nil)
|
||||
((eq (cvs-fileinfo->type a) 'DIRCHANGE) t)
|
||||
|
||||
;; All files are sorted by file name.
|
||||
((string< (cvs-fileinfo->file a) (cvs-fileinfo->file b))))))
|
||||
|
||||
(provide 'pcvs-info)
|
||||
|
||||
;;; pcl-cvs-info.el ends here
|
||||
478
lisp/pcvs-parse.el
Normal file
478
lisp/pcvs-parse.el
Normal file
|
|
@ -0,0 +1,478 @@
|
|||
;;; pcvs-parse.el --- The CVS output parser
|
||||
|
||||
;; Copyright (C) 1991-2000 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Stefan Monnier <monnier@cs.yale.edu>
|
||||
;; Keywords: pcl-cvs
|
||||
;; Version: $Name: $
|
||||
;; Revision: $Id: pcl-cvs-parse.el,v 1.41 2000/03/05 21:32:21 monnier Exp $
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs 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 2, or (at your option)
|
||||
;; any later version.
|
||||
|
||||
;; GNU Emacs 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 GNU Emacs; see the file COPYING. If not, write to the
|
||||
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
;; Boston, MA 02111-1307, USA.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
|
||||
;;; Code:
|
||||
|
||||
(eval-when-compile (require 'cl))
|
||||
|
||||
(require 'pcvs-util)
|
||||
(require 'pcvs-info)
|
||||
|
||||
;; imported from pcvs.el
|
||||
(defvar cvs-execute-single-dir)
|
||||
|
||||
;; parse vars
|
||||
|
||||
(defcustom cvs-update-prog-output-skip-regexp "$"
|
||||
"*A regexp that matches the end of the output from all cvs update programs.
|
||||
That is, output from any programs that are run by CVS (by the flag -u
|
||||
in the `modules' file - see cvs(5)) when `cvs update' is performed should
|
||||
terminate with a line that this regexp matches. It is enough that
|
||||
some part of the line is matched.
|
||||
|
||||
The default (a single $) fits programs without output."
|
||||
:group 'pcl-cvs
|
||||
:type '(regexp :value "$"))
|
||||
|
||||
(defcustom cvs-parse-ignored-messages
|
||||
'("Executing ssh-askpass to query the password.*$"
|
||||
".*Remote host denied X11 forwarding.*$")
|
||||
"*A list of regexps matching messages that should be ignored by the parser.
|
||||
Each regexp should match a whole set of lines and should hence be terminated
|
||||
by `$'."
|
||||
:group 'pcl-cvs
|
||||
:type '(repeat regexp))
|
||||
|
||||
;; a few more defvars just to shut up the compiler
|
||||
(defvar cvs-start)
|
||||
(defvar cvs-current-dir)
|
||||
(defvar cvs-current-subdir)
|
||||
(defvar dont-change-disc)
|
||||
|
||||
;;;; The parser
|
||||
|
||||
(defconst cvs-parse-known-commands
|
||||
'("status" "add" "commit" "update" "remove" "checkout" "ci")
|
||||
"List of CVS commands whose output is understood by the parser.")
|
||||
|
||||
(defun cvs-parse-buffer (parse-spec dont-change-disc &optional subdir)
|
||||
"Parse current buffer according to PARSE-SPEC.
|
||||
PARSE-SPEC is a function of no argument advancing the point and returning
|
||||
either a fileinfo or t (if the matched text should be ignored) or
|
||||
nil if it didn't match anything.
|
||||
DONT-CHANGE-DISC just indicates whether the command was changing the disc
|
||||
or not (useful to tell the difference btween `cvs-examine' and `cvs-update'
|
||||
ouytput.
|
||||
The path names should be interpreted as relative to SUBDIR (defaults
|
||||
to the `default-directory').
|
||||
Return a list of collected entries, or t if an error occured."
|
||||
(goto-char (point-min))
|
||||
(let ((fileinfos ())
|
||||
(cvs-current-dir "")
|
||||
(case-fold-search nil)
|
||||
(cvs-current-subdir (or subdir "")))
|
||||
(while (not (or (eobp) (eq fileinfos t)))
|
||||
(let ((ret (cvs-parse-run-table parse-spec)))
|
||||
(cond
|
||||
;; it matched a known information message
|
||||
((cvs-fileinfo-p ret) (push ret fileinfos))
|
||||
;; it didn't match anything at all (impossible)
|
||||
((and (consp ret) (cvs-fileinfo-p (car ret)))
|
||||
(setq fileinfos (append ret fileinfos)))
|
||||
((null ret) (setq fileinfos t))
|
||||
;; it matched something that should be ignored
|
||||
(t nil))))
|
||||
(nreverse fileinfos)))
|
||||
|
||||
|
||||
;; All those parsing macros/functions should return a success indicator
|
||||
(defsubst cvs-parse-msg () (buffer-substring cvs-start (1- (point))))
|
||||
|
||||
;;(defsubst COLLECT (exp) (push exp *result*))
|
||||
;;(defsubst PROG (e) t)
|
||||
;;(defmacro SEQ (&rest seqs) (cons 'and seqs))
|
||||
|
||||
(defmacro cvs-match (re &rest matches)
|
||||
"Try to match RE and extract submatches.
|
||||
If RE matches, advance the point until the line after the match and
|
||||
then assign the variables as specified in MATCHES (via `setq')."
|
||||
(cons 'cvs-do-match
|
||||
(cons re (mapcar (lambda (match)
|
||||
`(cons ',(first match) ,(second match)))
|
||||
matches))))
|
||||
|
||||
(defun cvs-do-match (re &rest matches)
|
||||
"Internal function for the `cvs-match' macro.
|
||||
Match RE and if successful, execute MATCHES."
|
||||
;; Is it a match?
|
||||
(when (looking-at re)
|
||||
(goto-char (match-end 0))
|
||||
;; Skip the newline (unless we already are at the end of the buffer).
|
||||
(when (and (eolp) (< (point) (point-max))) (forward-char))
|
||||
;; assign the matches
|
||||
(dolist (match matches t)
|
||||
(let ((val (cdr match)))
|
||||
(set (car match) (if (integerp val) (match-string val) val))))))
|
||||
|
||||
(defmacro cvs-or (&rest alts)
|
||||
"Try each one of the ALTS alternatives until one matches."
|
||||
`(let ((-cvs-parse-point (point)))
|
||||
,(cons 'or
|
||||
(mapcar (lambda (es)
|
||||
`(or ,es (ignore (goto-char -cvs-parse-point))))
|
||||
alts))))
|
||||
(def-edebug-spec cvs-or t)
|
||||
|
||||
;; This is how parser tables should be executed
|
||||
(defun cvs-parse-run-table (parse-spec)
|
||||
"Run PARSE-SPEC and provide sensible default behavior."
|
||||
(unless (bolp) (forward-line 1)) ;this should never be needed
|
||||
(let ((cvs-start (point)))
|
||||
(cvs-or
|
||||
(funcall parse-spec)
|
||||
|
||||
(dolist (re cvs-parse-ignored-messages)
|
||||
(when (cvs-match re) (return t)))
|
||||
|
||||
;; This is a parse error. Create a message-type fileinfo.
|
||||
(and
|
||||
(cvs-match ".*$")
|
||||
(cvs-create-fileinfo 'MESSAGE cvs-current-dir " "
|
||||
(concat " Parser Error: '" (cvs-parse-msg) "'")
|
||||
:subtype 'ERROR)))))
|
||||
|
||||
|
||||
(defun cvs-parsed-fileinfo (type path &optional directory &rest keys)
|
||||
"Create a fileinfo.
|
||||
TYPE can either be a type symbol or a cons of the form (TYPE . SUBTYPE).
|
||||
PATH is the filename.
|
||||
DIRECTORY influences the way PATH is interpreted:
|
||||
- if it's a string, it denotes the directory in which PATH (which should then be
|
||||
a plain file name with no directory component) resides.
|
||||
- if it's nil, the PATH should not be trusted: if it has a directory
|
||||
component, use it, else, assume it is relative to the current directory.
|
||||
- else, the PATH should be trusted to be relative to the root
|
||||
directory (i.e. if there is no directory component, it means the file
|
||||
is inside the main directory).
|
||||
The remaining KEYS are passed directly to `cvs-create-fileinfo'."
|
||||
(let ((dir directory)
|
||||
(file path))
|
||||
;; only trust the directory if it's a string
|
||||
(unless (stringp directory)
|
||||
;; else, if the directory is true, the path should be trusted
|
||||
(setq dir (or (file-name-directory path) (if directory "")))
|
||||
(setq file (file-name-nondirectory path)))
|
||||
|
||||
(let ((type (if (consp type) (car type) type))
|
||||
(subtype (if (consp type) (cdr type))))
|
||||
(when dir (setq cvs-current-dir dir))
|
||||
(apply 'cvs-create-fileinfo type
|
||||
(concat cvs-current-subdir (or dir cvs-current-dir))
|
||||
file (cvs-parse-msg) :subtype subtype keys))))
|
||||
|
||||
|
||||
;;;; CVS Process Parser Tables:
|
||||
;;;;
|
||||
;;;; The table for status and update could actually be merged since they
|
||||
;;;; don't conflict. But they don't overlap much either.
|
||||
|
||||
(defun cvs-parse-table ()
|
||||
"Table of message objects for `cvs-parse-process'."
|
||||
(let (c file dir path type base-rev subtype)
|
||||
(cvs-or
|
||||
|
||||
(cvs-parse-status)
|
||||
(cvs-parse-merge)
|
||||
(cvs-parse-commit)
|
||||
|
||||
;; this is not necessary because the fileinfo merging will remove
|
||||
;; such duplicate info and luckily the second info is the one we want.
|
||||
;; (and (cvs-match "M \\(.*\\)$" (path 1))
|
||||
;; (cvs-parse-merge path))
|
||||
|
||||
;; Normal file state indicator.
|
||||
(and
|
||||
(cvs-match "\\([MARCUPNJ?]\\) \\(.*\\)$" (c 1) (path 2))
|
||||
;; M: The file is modified by the user, and untouched in the repository.
|
||||
;; A: The file is "cvs add"ed, but not "cvs ci"ed.
|
||||
;; R: The file is "cvs remove"ed, but not "cvs ci"ed.
|
||||
;; C: Conflict
|
||||
;; U: The file is copied from the repository.
|
||||
;; P: The file was patched from the repository.
|
||||
;; ?: Unknown file.
|
||||
(let ((code (aref c 0)))
|
||||
(cvs-parsed-fileinfo (case code
|
||||
(?M 'MODIFIED)
|
||||
(?A 'ADDED)
|
||||
(?R 'REMOVED)
|
||||
(?? 'UNKNOWN)
|
||||
(?C 'CONFLICT) ;(if dont-change-disc 'NEED-MERGE
|
||||
(?J 'NEED-MERGE) ;not supported by standard CVS
|
||||
((?U ?P)
|
||||
(if dont-change-disc
|
||||
'NEED-UPDATE
|
||||
(cons 'UP-TO-DATE
|
||||
(if (eq code ?U) 'UPDATED 'PATCHED)))))
|
||||
path 'trust)))
|
||||
|
||||
(and
|
||||
(cvs-match "pcl-cvs: descending directory \\(.*\\)$" (dir 1))
|
||||
(setq cvs-current-subdir dir))
|
||||
|
||||
;; A special cvs message
|
||||
(and
|
||||
(cvs-match "cvs[.ex]* [a-z]+: ")
|
||||
(cvs-or
|
||||
|
||||
;; CVS is descending a subdirectory
|
||||
;; (status says `examining' while update says `updating')
|
||||
(and
|
||||
(cvs-match "\\(Examining\\|Updating\\) \\(.*\\)$" (dir 2))
|
||||
(let ((dir (if (string= "." dir) "" (file-name-as-directory dir))))
|
||||
(cvs-parsed-fileinfo 'DIRCHANGE "." dir)))
|
||||
|
||||
;; [-n update] A new (or pruned) directory appeared but isn't traversed
|
||||
(and
|
||||
(cvs-match "New directory `\\(.*\\)' -- ignored$" (dir 1))
|
||||
(cvs-parsed-fileinfo 'MESSAGE " " (file-name-as-directory dir)))
|
||||
|
||||
;; File removed, since it is removed (by third party) in repository.
|
||||
(and
|
||||
(cvs-or
|
||||
(cvs-match "warning: \\(.*\\) is not (any longer) pertinent$" (file 1))
|
||||
(cvs-match "\\(.*\\) is no longer in the repository$" (file 1)))
|
||||
(cvs-parsed-fileinfo 'DEAD file))
|
||||
|
||||
;; [add]
|
||||
(and
|
||||
(cvs-or
|
||||
(cvs-match "scheduling file `\\(.*\\)' for addition.*$" (path 1))
|
||||
(cvs-match "re-adding file \\(.*\\) (in place of .*)$" (path 1)))
|
||||
(cvs-parsed-fileinfo 'ADDED path))
|
||||
|
||||
;; [add] this will also show up as a `U <file>'
|
||||
(and
|
||||
(cvs-match "\\(.*\\), version \\(.*\\), resurrected$"
|
||||
(path 1) (base-rev 2))
|
||||
(cvs-parsed-fileinfo '(UP-TO-DATE . RESURRECTED) path nil
|
||||
:base-rev base-rev))
|
||||
|
||||
;; [remove]
|
||||
(and
|
||||
(cvs-match "removed `\\(.*\\)'$" (path 1))
|
||||
(cvs-parsed-fileinfo 'DEAD path))
|
||||
|
||||
;; [remove,merge]
|
||||
(and
|
||||
(cvs-match "scheduling `\\(.*\\)' for removal$" (file 1))
|
||||
(cvs-parsed-fileinfo 'REMOVED file))
|
||||
|
||||
;; [update] File removed by you, but not cvs rm'd
|
||||
(and
|
||||
(cvs-match "warning: \\(.*\\) was lost$" (path 1))
|
||||
(cvs-match (concat "U " (regexp-quote path) "$"))
|
||||
(cvs-parsed-fileinfo (if dont-change-disc
|
||||
'MISSING
|
||||
'(UP-TO-DATE . UPDATED))
|
||||
path))
|
||||
|
||||
;; Mode conflicts (rather than contents)
|
||||
(and
|
||||
(cvs-match "conflict: ")
|
||||
(cvs-or
|
||||
(cvs-match "removed \\(.*\\) was modified by second party$"
|
||||
(path 1) (subtype 'REMOVED))
|
||||
(cvs-match "\\(.*\\) created independently by second party$"
|
||||
(path 1) (subtype 'ADDED))
|
||||
(cvs-match "\\(.*\\) is modified but no longer in the repository$"
|
||||
(path 1) (subtype 'MODIFIED)))
|
||||
(cvs-match (concat "C " (regexp-quote path)))
|
||||
(cvs-parsed-fileinfo (cons 'CONFLICT subtype) path))
|
||||
|
||||
;; Messages that should be shown to the user
|
||||
(and
|
||||
(cvs-or
|
||||
(cvs-match "move away \\(.*\\); it is in the way$" (file 1))
|
||||
(cvs-match "warning: new-born \\(.*\\) has disappeared$" (file 1))
|
||||
(cvs-match "sticky tag .* for file `\\(.*\\)' is not a branch$"
|
||||
(file 1)))
|
||||
(cvs-parsed-fileinfo 'MESSAGE file))
|
||||
|
||||
;; File unknown.
|
||||
(and (cvs-match "use `.+ add' to create an entry for \\(.*\\)$" (path 1))
|
||||
(cvs-parsed-fileinfo 'UNKNOWN path))
|
||||
|
||||
;; We use cvs-execute-multi-dir but cvs can't handle it
|
||||
;; Probably because the cvs-client can but the cvs-server can't
|
||||
(and (cvs-match ".* files with '?/'? in their name.*$")
|
||||
(not cvs-execute-single-dir)
|
||||
(setq cvs-execute-single-dir t)
|
||||
(cvs-create-fileinfo
|
||||
'MESSAGE "" " "
|
||||
"*** Add (setq cvs-execute-single-dir t) to your .emacs ***
|
||||
See the FAQ file or the variable's documentation for more info."))
|
||||
|
||||
;; Cvs waits for a lock. Ignored: already handled by the process filter
|
||||
(cvs-match "\\[..:..:..\\] \\(waiting for\\|obtained\\) .*lock in .*$")
|
||||
;; File you removed still exists. Ignore (will be noted as removed).
|
||||
(cvs-match ".* should be removed and is still there$")
|
||||
;; just a note
|
||||
(cvs-match "use '.+ commit' to \\sw+ th\\sw+ files? permanently$")
|
||||
;; [add,status] followed by a more complete status description anyway
|
||||
(cvs-match "nothing known about .*$")
|
||||
;; [update] problem with patch
|
||||
(cvs-match "checksum failure after patch to .*; will refetch$")
|
||||
(cvs-match "refetching unpatchable files$")
|
||||
;; [commit]
|
||||
(cvs-match "Rebuilding administrative file database$")
|
||||
|
||||
;; CVS is running a *info program.
|
||||
(and
|
||||
(cvs-match "Executing.*$")
|
||||
;; Skip by any output the program may generate to stdout.
|
||||
;; Note that pcl-cvs will get seriously confused if the
|
||||
;; program prints anything to stderr.
|
||||
(re-search-forward cvs-update-prog-output-skip-regexp))))
|
||||
|
||||
(and
|
||||
(cvs-match "cvs[.ex]* \\[[a-z]+ aborted\\]:.*$")
|
||||
(cvs-parsed-fileinfo 'MESSAGE ""))
|
||||
|
||||
;; sadly you can't do much with these since the path is in the repository
|
||||
(cvs-match "Directory .* added to the repository$")
|
||||
)))
|
||||
|
||||
|
||||
(defun cvs-parse-merge ()
|
||||
(let (path base-rev head-rev handled type)
|
||||
;; A merge (maybe with a conflict).
|
||||
(and
|
||||
(cvs-match "RCS file: .*$")
|
||||
;; Squirrel away info about the files that were retrieved for merging
|
||||
(cvs-match "retrieving revision \\([0-9.]+\\)$" (base-rev 1))
|
||||
(cvs-match "retrieving revision \\([0-9.]+\\)$" (head-rev 1))
|
||||
(cvs-match "Merging differences between [0-9.]+ and [0-9.]+ into \\(.*\\)$"
|
||||
(path 1))
|
||||
|
||||
;; eat up potential conflict warnings
|
||||
(cvs-or (cvs-match "\\(rcs\\)?merge:?\\( warning\\)?: \\(overlaps\\|conflicts\\) \\(or other problems \\)?during merge$" (type 'CONFLICT)) t)
|
||||
(cvs-or
|
||||
(and
|
||||
(cvs-match "cvs[.ex]* [a-z]+: ")
|
||||
(cvs-or
|
||||
(cvs-match "conflicts found in \\(.*\\)$" (path 1) (type 'CONFLICT))
|
||||
(cvs-match "could not merge .*$")
|
||||
(cvs-match "restoring \\(.*\\) from backup file .*$" (path 1))))
|
||||
t)
|
||||
|
||||
;; Is it a succesful merge?
|
||||
;; Figure out result of merging (ie, was there a conflict?)
|
||||
(let ((qfile (regexp-quote path)))
|
||||
(cvs-or
|
||||
;; Conflict
|
||||
(and
|
||||
(cvs-match (concat "C \\(.*" qfile "\\)$") (path 1) (type 'CONFLICT))
|
||||
;; C might be followed by a "suprious" U for non-mergeable files
|
||||
(cvs-or (cvs-match (concat "U \\(.*" qfile "\\)$")) t))
|
||||
;; Successful merge
|
||||
(cvs-match (concat "M \\(.*" qfile "\\)$") (path 1))
|
||||
;; The file already contained the modifications
|
||||
(cvs-match (concat "^\\(.*" qfile
|
||||
"\\) already contains the differences between .*$")
|
||||
(path 1) (type '(UP-TO-DATE . MERGED)))
|
||||
t)
|
||||
(cvs-parsed-fileinfo (if dont-change-disc 'NEED-MERGE
|
||||
(or type '(MODIFIED . MERGED))) path nil
|
||||
:merge (cons base-rev head-rev))))))
|
||||
|
||||
(defun cvs-parse-status ()
|
||||
(let (nofile path base-rev head-rev type)
|
||||
(and
|
||||
(cvs-match
|
||||
"===================================================================$")
|
||||
(cvs-match "File: \\(no file \\)?\\(.*[^ \t]\\)[ \t]+Status: "
|
||||
(nofile 1) (path 2))
|
||||
(cvs-or
|
||||
(cvs-match "Needs \\(Checkout\\|Patch\\)$"
|
||||
(type (if nofile 'MISSING 'NEED-UPDATE)))
|
||||
(cvs-match "Up-to-date$"
|
||||
(type (if nofile '(UP-TO-DATE . REMOVED) 'UP-TO-DATE)))
|
||||
(cvs-match ".*[Cc]onflict.*$" (type 'CONFLICT))
|
||||
(cvs-match "Locally Added$" (type 'ADDED))
|
||||
(cvs-match "Locally Removed$" (type 'REMOVED))
|
||||
(cvs-match "Locally Modified$" (type 'MODIFIED))
|
||||
(cvs-match "Needs Merge$" (type 'NEED-MERGE))
|
||||
(cvs-match "Unknown$" (type 'UNKNOWN)))
|
||||
(cvs-match "$")
|
||||
(cvs-or
|
||||
(cvs-match " *Version:[ \t]*\\([0-9.]+\\).*$" (base-rev 1))
|
||||
;; NOTE: there's no date on the end of the following for server mode...
|
||||
(cvs-match " *Working revision:[ \t]*-?\\([0-9.]+\\).*$" (base-rev 1))
|
||||
;; Let's not get all worked up if the format changes a bit
|
||||
(cvs-match " *Working revision:.*$"))
|
||||
(cvs-or
|
||||
(cvs-match " *RCS Version:[ \t]*\\([0-9.]+\\)[ \t]*.*$" (head-rev 1))
|
||||
(cvs-match " *Repository revision:[ \t]*\\([0-9.]+\\)[ \t]*\\(.*\\)$"
|
||||
(head-rev 1))
|
||||
(cvs-match " *Repository revision:.*"))
|
||||
(cvs-or
|
||||
(and;;sometimes those fields are missing
|
||||
(cvs-match " *Sticky Tag:[ \t]*\\(.*\\)$") ; FIXME: use it
|
||||
(cvs-match " *Sticky Date:[ \t]*\\(.*\\)$") ; FIXME: use it
|
||||
(cvs-match " *Sticky Options:[ \t]*\\(.*\\)$")) ; FIXME: use it
|
||||
t)
|
||||
(cvs-match "$")
|
||||
;; ignore the tags-listing in the case of `status -v'
|
||||
(cvs-or (cvs-match " *Existing Tags:\n\\(\t.*\n\\)*$") t)
|
||||
(cvs-parsed-fileinfo type path nil
|
||||
:base-rev base-rev
|
||||
:head-rev head-rev))))
|
||||
|
||||
(defun cvs-parse-commit ()
|
||||
(let (path base-rev subtype)
|
||||
(cvs-or
|
||||
|
||||
(and
|
||||
(cvs-match "\\(Checking in\\|Removing\\) \\(.*\\);$" (path 2))
|
||||
(cvs-match ".*,v <-- .*$")
|
||||
(cvs-or
|
||||
;; deletion
|
||||
(cvs-match "new revision: delete; previous revision: \\([0-9.]*\\)$"
|
||||
(subtype 'REMOVED) (base-rev 1))
|
||||
;; addition
|
||||
(cvs-match "initial revision: \\([0-9.]*\\)$"
|
||||
(subtype 'ADDED) (base-rev 1))
|
||||
;; update
|
||||
(cvs-match "new revision: \\([0-9.]*\\); previous revision: .*$"
|
||||
(subtype 'COMMITTED) (base-rev 1)))
|
||||
(cvs-match "done$")
|
||||
;; it's important here not to rely on the default directory management
|
||||
;; because `cvs commit' might begin by a series of Examining messages
|
||||
;; so the processing of the actual checkin messages might begin with
|
||||
;; a `current-dir' set to something different from ""
|
||||
(cvs-parsed-fileinfo (cons 'UP-TO-DATE subtype) path 'trust
|
||||
:base-rev base-rev))
|
||||
|
||||
;; useless message added before the actual addition: ignored
|
||||
(cvs-match "RCS file: .*\ndone$"))))
|
||||
|
||||
|
||||
(provide 'pcvs-parse)
|
||||
|
||||
;;; pcl-cvs-parse.el ends here
|
||||
381
lisp/pcvs-util.el
Normal file
381
lisp/pcvs-util.el
Normal file
|
|
@ -0,0 +1,381 @@
|
|||
;;; pcvs-util.el --- Utitlity functions for pcl-cvs
|
||||
|
||||
;; Copyright (C) 1998-2000 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Stefan Monnier <monnier@cs.yale.edu>
|
||||
;; Keywords: pcl-cvs
|
||||
;; Version: $Name: $
|
||||
;; Revision: $Id: pcl-cvs-util.el,v 1.26 2000/03/05 21:32:21 monnier Exp $
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs 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 2, or (at your option)
|
||||
;; any later version.
|
||||
|
||||
;; GNU Emacs 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 GNU Emacs; see the file COPYING. If not, write to the
|
||||
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
;; Boston, MA 02111-1307, USA.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
|
||||
;;; Code:
|
||||
|
||||
(eval-when-compile (require 'cl))
|
||||
|
||||
;;;;
|
||||
;;;; list processing
|
||||
;;;l
|
||||
|
||||
(defsubst cvs-car (x) (if (consp x) (car x) x))
|
||||
(defalias 'cvs-cdr 'cdr-safe)
|
||||
(defsubst cvs-append (&rest xs)
|
||||
(apply 'append (mapcar (lambda (x) (if (listp x) x (list x))) xs)))
|
||||
|
||||
(defsubst cvs-every (-cvs-every-f -cvs-every-l)
|
||||
(while (consp -cvs-every-l)
|
||||
(unless (funcall -cvs-every-f (pop -cvs-every-l))
|
||||
(setq -cvs-every-l t)))
|
||||
(not -cvs-every-l))
|
||||
|
||||
(defun cvs-union (xs ys)
|
||||
(let ((zs ys))
|
||||
(dolist (x xs zs)
|
||||
(unless (member x ys) (push x zs)))))
|
||||
|
||||
|
||||
(defun cvs-map (-cvs-map-f &rest -cvs-map-ls)
|
||||
(unless (cvs-every 'null -cvs-map-ls)
|
||||
(cons (apply -cvs-map-f (mapcar 'car -cvs-map-ls))
|
||||
(apply 'cvs-map -cvs-map-f (mapcar 'cdr -cvs-map-ls)))))
|
||||
|
||||
(defun cvs-first (l &optional n)
|
||||
(if (null n) (car l)
|
||||
(when l
|
||||
(let* ((nl (list (pop l)))
|
||||
(ret nl))
|
||||
(while (and l (> n 1))
|
||||
(setcdr nl (list (pop l)))
|
||||
(setq nl (cdr nl))
|
||||
(decf n))
|
||||
ret))))
|
||||
|
||||
(defun cvs-partition (p l)
|
||||
"Partition a list L into two lists based on predicate P.
|
||||
The function returns a `cons' cell where the `car' contains
|
||||
elements of L for which P is true while the `cdr' contains
|
||||
the other elements. The ordering among elements is maintained."
|
||||
(let (car cdr)
|
||||
(dolist (x l)
|
||||
(if (funcall p x) (push x car) (push x cdr)))
|
||||
(cons (nreverse car) (nreverse cdr))))
|
||||
|
||||
;;;;
|
||||
;;;; frame, window, buffer handling
|
||||
;;;;
|
||||
|
||||
(defun cvs-pop-to-buffer-same-frame (buf)
|
||||
"Pop to BUF like `pop-to-buffer' but staying on the same frame.
|
||||
If `pop-to-buffer' would have opened a new frame, this function would
|
||||
try to split the a new window instead."
|
||||
(let ((pop-up-windows (or pop-up-windows pop-up-frames))
|
||||
(pop-up-frames nil))
|
||||
(or (let ((buf (get-buffer-window buf))) (and buf (select-window buf)))
|
||||
(and pop-up-windows
|
||||
(ignore-errors (select-window (split-window-vertically)))
|
||||
(switch-to-buffer buf))
|
||||
(pop-to-buffer (current-buffer)))))
|
||||
|
||||
(defun cvs-bury-buffer (buf &optional mainbuf)
|
||||
"Hide the buffer BUF that was temporarily popped up.
|
||||
BUF is assumed to be a temporary buffer used from the buffer MAINBUF."
|
||||
(interactive (list (current-buffer)))
|
||||
(save-current-buffer
|
||||
(let ((win (if (eq buf (window-buffer (selected-window))) (selected-window)
|
||||
(get-buffer-window buf t))))
|
||||
(when win
|
||||
(if (window-dedicated-p win)
|
||||
(condition-case ()
|
||||
(delete-window win)
|
||||
(error (iconify-frame (window-frame win))))
|
||||
(if (and mainbuf (get-buffer-window mainbuf))
|
||||
(delete-window win)))))
|
||||
(with-current-buffer buf
|
||||
(bury-buffer (unless (and (eq buf (window-buffer (selected-window)))
|
||||
(not (window-dedicated-p (selected-window))))
|
||||
buf)))
|
||||
(when mainbuf
|
||||
(let ((mainwin (or (get-buffer-window mainbuf)
|
||||
(get-buffer-window mainbuf 'visible))))
|
||||
(when mainwin (select-window mainwin))))))
|
||||
|
||||
(defun cvs-get-buffer-create (name &optional noreuse)
|
||||
"Create a buffer NAME unless such a buffer already exists.
|
||||
If the NAME looks like an absolute file name, the buffer will be created
|
||||
with `create-file-buffer' and will probably get another name than NAME.
|
||||
In such a case, the search for another buffer with the same name doesn't
|
||||
use the buffer name but the buffer's `list-buffers-directory' variable.
|
||||
If NOREUSE is non-nil, always return a new buffer."
|
||||
(or (and (not (file-name-absolute-p name)) (get-buffer-create name))
|
||||
(unless noreuse
|
||||
(dolist (buf (buffer-list))
|
||||
(with-current-buffer buf
|
||||
(when (equal name list-buffers-directory)
|
||||
(return buf)))))
|
||||
(with-current-buffer (create-file-buffer name)
|
||||
(set (make-local-variable 'list-buffers-directory) name)
|
||||
(current-buffer))))
|
||||
|
||||
;;;;
|
||||
;;;; string processing
|
||||
;;;;
|
||||
|
||||
(defun cvs-file-to-string (file &optional oneline args)
|
||||
"Read the content of FILE and return it as a string.
|
||||
If ONELINE is t, only the first line (no \\n) will be returned.
|
||||
If ARGS is non-nil, the file will be executed with ARGS as its
|
||||
arguments. If ARGS is not a list, no argument will be passed."
|
||||
(with-temp-buffer
|
||||
(condition-case nil
|
||||
(progn
|
||||
(if args
|
||||
(apply 'call-process
|
||||
file nil t nil (when (listp args) args))
|
||||
(insert-file-contents file))
|
||||
(buffer-substring (point-min)
|
||||
(if oneline
|
||||
(progn (goto-char (point-min)) (end-of-line) (point))
|
||||
(point-max))))
|
||||
(file-error nil))))
|
||||
|
||||
(defun cvs-string-prefix-p (str1 str2)
|
||||
"Tell whether STR1 is a prefix of STR2."
|
||||
(let ((length1 (length str1)))
|
||||
(and (>= (length str2) length1)
|
||||
(string= str1 (substring str2 0 length1)))))
|
||||
|
||||
;; (string->strings (strings->string X)) == X
|
||||
(defun cvs-strings->string (strings &optional separator)
|
||||
"Concatenate the STRINGS, adding the SEPARATOR (default \" \").
|
||||
This tries to quote the strings to avoid ambiguity such that
|
||||
(cvs-string->strings (cvs-strings->string strs)) == strs
|
||||
Only some SEPARATOR will work properly."
|
||||
(let ((sep (or separator " ")))
|
||||
(mapconcat
|
||||
(lambda (str)
|
||||
(if (string-match "[\\\"]" str)
|
||||
(concat "\"" (replace-regexps-in-string "[\\\"]" "\\\\\\&" str) "\"")
|
||||
str))
|
||||
strings sep)))
|
||||
|
||||
;; (string->strings (strings->string X)) == X
|
||||
(defun cvs-string->strings (string &optional separator)
|
||||
"Split the STRING into a list of strings.
|
||||
It understands elisp style quoting within STRING such that
|
||||
(cvs-string->strings (cvs-strings->string strs)) == strs
|
||||
The SEPARATOR regexp defaults to \"\\s-+\"."
|
||||
(let ((sep (or separator "\\s-+"))
|
||||
(i (string-match "[\"]" string)))
|
||||
(if (null i) (split-string string sep) ; no quoting: easy
|
||||
(append (unless (eq i 0) (split-string (substring string 0 i) sep))
|
||||
(let ((rfs (read-from-string string i)))
|
||||
(cons (car rfs)
|
||||
(cvs-string->strings (substring string (cdr rfs)) sep)))))))
|
||||
|
||||
|
||||
(defun cvs-string-fill (str n &optional filling truncate)
|
||||
"Add FILLING (defaults to the space char) to STR to reach size N.
|
||||
If STR is longer than N, truncate if TRUNCATE is set, else don't do anything."
|
||||
(let ((l (length str)))
|
||||
(if (> l n)
|
||||
(if truncate (substring str 0 n) str)
|
||||
(concat str (make-string (- n l) (or filling ? ))))))
|
||||
|
||||
;;;;
|
||||
;;;; file names
|
||||
;;;;
|
||||
|
||||
(defsubst cvs-expand-dir-name (d)
|
||||
(file-name-as-directory (expand-file-name d)))
|
||||
|
||||
;;;;
|
||||
;;;; (interactive <foo>) support function
|
||||
;;;;
|
||||
|
||||
(defstruct (cvs-qtypedesc
|
||||
(:constructor nil) (:copier nil)
|
||||
(:constructor cvs-qtypedesc-create
|
||||
(str2obj obj2str &optional complete hist-sym require)))
|
||||
str2obj
|
||||
obj2str
|
||||
hist-sym
|
||||
complete
|
||||
require)
|
||||
|
||||
|
||||
(defconst cvs-qtypedesc-string1 (cvs-qtypedesc-create 'identity 'identity t))
|
||||
(defconst cvs-qtypedesc-string (cvs-qtypedesc-create 'identity 'identity))
|
||||
(defconst cvs-qtypedesc-strings
|
||||
(cvs-qtypedesc-create 'cvs-string->strings 'cvs-strings->string nil))
|
||||
|
||||
(defun cvs-query-read (default prompt qtypedesc &optional hist-sym)
|
||||
(let* ((qtypedesc (or qtypedesc cvs-qtypedesc-strings))
|
||||
(hist-sym (or hist-sym (cvs-qtypedesc-hist-sym qtypedesc)))
|
||||
(complete (cvs-qtypedesc-complete qtypedesc))
|
||||
(completions (and (functionp complete) (funcall complete)))
|
||||
(initval (funcall (cvs-qtypedesc-obj2str qtypedesc) default)))
|
||||
(funcall (cvs-qtypedesc-str2obj qtypedesc)
|
||||
(cond
|
||||
((null complete) (read-string prompt initval hist-sym))
|
||||
((functionp complete)
|
||||
(completing-read prompt completions
|
||||
nil (cvs-qtypedesc-require qtypedesc)
|
||||
initval hist-sym))
|
||||
(t initval)))))
|
||||
|
||||
;;;;
|
||||
;;;; Flags handling
|
||||
;;;;
|
||||
|
||||
(defstruct (cvs-flags
|
||||
(:constructor nil)
|
||||
(:constructor -cvs-flags-make
|
||||
(desc defaults &optional qtypedesc hist-sym)))
|
||||
defaults persist desc qtypedesc hist-sym)
|
||||
|
||||
(defmacro cvs-flags-define (sym defaults
|
||||
&optional desc qtypedesc hist-sym docstring)
|
||||
`(defconst ,sym
|
||||
(let ((bound (boundp ',sym)))
|
||||
(if (and bound (cvs-flags-p ,sym)) ,sym
|
||||
(let ((defaults ,defaults))
|
||||
(-cvs-flags-make ,desc
|
||||
(if bound (cons ,sym (cdr defaults)) defaults)
|
||||
,qtypedesc ,hist-sym))))
|
||||
,docstring))
|
||||
|
||||
(defun cvs-flags-query (sym &optional desc arg)
|
||||
"Query flags based on SYM.
|
||||
Optional argument DESC will be used for the prompt
|
||||
If ARG (or a prefix argument) is nil, just use the 0th default.
|
||||
If it is a non-negative integer, use the corresponding default.
|
||||
If it is a negative integer query for a new value of the corresponding
|
||||
default and return that new value.
|
||||
If it is \\[universal-argument], just query and return a value without
|
||||
altering the defaults.
|
||||
If it is \\[universal-argument] \\[universal-argument], behave just
|
||||
as if a negative zero was provided."
|
||||
(let* ((flags (symbol-value sym))
|
||||
(desc (or desc (cvs-flags-desc flags)))
|
||||
(qtypedesc (cvs-flags-qtypedesc flags))
|
||||
(hist-sym (cvs-flags-hist-sym flags))
|
||||
(arg (if (eq arg 'noquery) 0 (or arg current-prefix-arg 0)))
|
||||
(numarg (prefix-numeric-value arg))
|
||||
(defaults (cvs-flags-defaults flags))
|
||||
(permstr (if (< numarg 0) (format " (%sth default)" (- numarg)))))
|
||||
;; special case for universal-argument
|
||||
(when (consp arg)
|
||||
(setq permstr (if (> numarg 4) " (permanent)" ""))
|
||||
(setq numarg 0))
|
||||
|
||||
;; sanity check
|
||||
(unless (< (abs numarg) (length defaults))
|
||||
(error "There is no %sth default." (abs numarg)))
|
||||
|
||||
(if permstr
|
||||
(let* ((prompt (format "%s%s: " desc permstr))
|
||||
(fs (cvs-query-read (nth (- numarg) (cvs-flags-defaults flags))
|
||||
prompt qtypedesc hist-sym)))
|
||||
(when (not (equal permstr ""))
|
||||
(setf (nth (- numarg) (cvs-flags-defaults flags)) fs))
|
||||
fs)
|
||||
(nth numarg defaults))))
|
||||
|
||||
(defsubst cvs-flags-set (sym index value)
|
||||
"Set SYM's INDEX'th setting to VALUE."
|
||||
(setf (nth index (cvs-flags-defaults (symbol-value sym))) value))
|
||||
|
||||
;;;;
|
||||
;;;; Prefix keys
|
||||
;;;;
|
||||
|
||||
(defconst cvs-prefix-number 10)
|
||||
|
||||
(defsubst cvs-prefix-sym (sym) (intern (concat (symbol-name sym) "-cps")))
|
||||
|
||||
(defmacro cvs-prefix-define (sym docstring desc defaults
|
||||
&optional qtypedesc hist-sym)
|
||||
(let ((cps (cvs-prefix-sym sym)))
|
||||
`(progn
|
||||
(defvar ,sym nil ,(cons (or docstring "") "
|
||||
See `cvs-prefix-set' for further description of the behavior."))
|
||||
(defconst ,cps
|
||||
(let ((defaults ,defaults))
|
||||
;; sanity ensurance
|
||||
(unless (>= (length defaults) cvs-prefix-number)
|
||||
(setq defaults (append defaults
|
||||
(make-list (1- cvs-prefix-number)
|
||||
(first defaults)))))
|
||||
(-cvs-flags-make ,desc defaults ,qtypedesc ,hist-sym))))))
|
||||
|
||||
(defun cvs-prefix-make-local (sym)
|
||||
(let ((cps (cvs-prefix-sym sym)))
|
||||
(make-local-variable sym)
|
||||
(set (make-local-variable cps) (copy-cvs-flags (symbol-value cps)))))
|
||||
|
||||
(defun cvs-prefix-set (sym arg)
|
||||
;; we could distinguish between numeric and non-numeric prefix args instead of
|
||||
;; relying on that magic `4'.
|
||||
"Set the cvs-prefix contained in SYM.
|
||||
If ARG is between 0 and 9, it selects the corresponding default.
|
||||
If ARG is negative (or \\[universal-argument] which corresponds to negative 0),
|
||||
it queries the user and sets the -ARG'th default.
|
||||
If ARG is greater than 9 (or \\[universal-argument] \\[universal-argument]),
|
||||
the (ARG mod 10)'th prefix is made persistent.
|
||||
If ARG is NIL toggle the PREFIX's value between its 0th default and NIL
|
||||
and reset the persistence."
|
||||
(let* ((prefix (symbol-value (cvs-prefix-sym sym)))
|
||||
(numarg (if (integerp arg) arg 0))
|
||||
(defs (cvs-flags-defaults prefix)))
|
||||
|
||||
;; set persistence if requested
|
||||
(when (> (prefix-numeric-value arg) 9)
|
||||
(setf (cvs-flags-persist prefix) t)
|
||||
(setq numarg (mod numarg 10)))
|
||||
|
||||
;; set the value
|
||||
(set sym
|
||||
(cond
|
||||
((null arg)
|
||||
(setf (cvs-flags-persist prefix) nil)
|
||||
(unless (symbol-value sym) (first (cvs-flags-defaults prefix))))
|
||||
|
||||
((or (consp arg) (< numarg 0))
|
||||
(setf (nth (- numarg) (cvs-flags-defaults prefix))
|
||||
(cvs-query-read (nth (- numarg) (cvs-flags-defaults prefix))
|
||||
(format "%s: " (cvs-flags-desc prefix))
|
||||
(cvs-flags-qtypedesc prefix)
|
||||
(cvs-flags-hist-sym prefix))))
|
||||
(t (nth numarg (cvs-flags-defaults prefix)))))
|
||||
(force-mode-line-update)))
|
||||
|
||||
(defun cvs-prefix-get (sym &optional read-only)
|
||||
"Return the current value of the prefix SYM.
|
||||
and reset it unless READ-ONLY is non-nil."
|
||||
(prog1 (symbol-value sym)
|
||||
(unless (or read-only
|
||||
(cvs-flags-persist (symbol-value (cvs-prefix-sym sym))))
|
||||
(set sym nil)
|
||||
(force-mode-line-update))))
|
||||
|
||||
(provide 'pcvs-util)
|
||||
|
||||
;;; pcl-cvs-util.el ends here
|
||||
2122
lisp/pcvs.el
Normal file
2122
lisp/pcvs.el
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue