diff options
| author | Stefan Monnier | 2000-03-11 03:51:31 +0000 |
|---|---|---|
| committer | Stefan Monnier | 2000-03-11 03:51:31 +0000 |
| commit | 5b467bf4e2787e3290280cadbae9e915df88dacd (patch) | |
| tree | 83e838669d3052e213f8f518602bae5ec0cf0a15 | |
| parent | afa18a4e5d28a418fa9374c96be75a8e20f5fe08 (diff) | |
| download | emacs-5b467bf4e2787e3290280cadbae9e915df88dacd.tar.gz emacs-5b467bf4e2787e3290280cadbae9e915df88dacd.zip | |
*** empty log message ***
| -rw-r--r-- | lisp/ChangeLog | 13 | ||||
| -rw-r--r-- | lisp/cvs-status.el | 523 | ||||
| -rw-r--r-- | lisp/emacs-lisp/ewoc.el | 620 | ||||
| -rw-r--r-- | lisp/log-edit.el | 448 | ||||
| -rw-r--r-- | lisp/log-view.el | 189 | ||||
| -rw-r--r-- | lisp/pcvs-defs.el | 501 | ||||
| -rw-r--r-- | lisp/pcvs-info.el | 455 | ||||
| -rw-r--r-- | lisp/pcvs-parse.el | 478 | ||||
| -rw-r--r-- | lisp/pcvs-util.el | 381 | ||||
| -rw-r--r-- | lisp/pcvs.el | 2122 |
10 files changed, 5730 insertions, 0 deletions
diff --git a/lisp/ChangeLog b/lisp/ChangeLog index faa11a66aea..db0cc19d533 100644 --- a/lisp/ChangeLog +++ b/lisp/ChangeLog | |||
| @@ -1,3 +1,16 @@ | |||
| 1 | 2000-03-10 Stefan Monnier <monnier@cs.yale.edu> | ||
| 2 | |||
| 3 | * cvs-status, log-edit.el, log-view.el, pcvs-defs.el, pcvs-info.el, | ||
| 4 | pcvs-parse.el, pcvs-util.el, pcvs.el: New files. | ||
| 5 | |||
| 6 | * emacs-lisp/ewoc.el: New file. This is a merge of elib-node.el, dll.el | ||
| 7 | and cookie.el (from Elib) with heavy renaming and other massaging. | ||
| 8 | |||
| 9 | * emacs-lisp/easy-mmode.el (easy-mmode-defmap, easy-mmode-defsyntax): | ||
| 10 | Autoload the functions used. | ||
| 11 | (easy-mmode-define-syntax): Fix CL typo. | ||
| 12 | (easy-mmode-define-derived-mode): Improve the docstring generation. | ||
| 13 | |||
| 1 | 2000-03-10 Gerd Moellmann <gerd@gnu.org> | 14 | 2000-03-10 Gerd Moellmann <gerd@gnu.org> |
| 2 | 15 | ||
| 3 | * textmodes/texinfo.el (texinfo-version): Variable and function | 16 | * textmodes/texinfo.el (texinfo-version): Variable and function |
diff --git a/lisp/cvs-status.el b/lisp/cvs-status.el new file mode 100644 index 00000000000..33a6f6a1cfa --- /dev/null +++ b/lisp/cvs-status.el | |||
| @@ -0,0 +1,523 @@ | |||
| 1 | ;;; cvs-status.el --- Major mode for browsing `cvs status' output | ||
| 2 | |||
| 3 | ;; Copyright (C) 1999-2000 Free Software Foundation, Inc. | ||
| 4 | |||
| 5 | ;; Author: Stefan Monnier <monnier@cs.yale.edu> | ||
| 6 | ;; Keywords: pcl-cvs cvs status tree | ||
| 7 | ;; Version: $Name: $ | ||
| 8 | ;; Revision: $Id: cvs-status.el,v 1.14 2000/03/05 21:32:21 monnier Exp $ | ||
| 9 | |||
| 10 | ;; This file is part of GNU Emacs. | ||
| 11 | |||
| 12 | ;; GNU Emacs is free software; you can redistribute it and/or modify | ||
| 13 | ;; it under the terms of the GNU General Public License as published by | ||
| 14 | ;; the Free Software Foundation; either version 2, or (at your option) | ||
| 15 | ;; any later version. | ||
| 16 | |||
| 17 | ;; GNU Emacs is distributed in the hope that it will be useful, | ||
| 18 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 19 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 20 | ;; GNU General Public License for more details. | ||
| 21 | |||
| 22 | ;; You should have received a copy of the GNU General Public License | ||
| 23 | ;; along with GNU Emacs; see the file COPYING. If not, write to the | ||
| 24 | ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, | ||
| 25 | ;; Boston, MA 02111-1307, USA. | ||
| 26 | |||
| 27 | ;;; Commentary: | ||
| 28 | |||
| 29 | ;; Todo: | ||
| 30 | |||
| 31 | ;; - Rename to cvs-status-mode.el | ||
| 32 | ;; - Somehow allow cvs-status-tree to work on-the-fly | ||
| 33 | |||
| 34 | ;;; Code: | ||
| 35 | |||
| 36 | (eval-when-compile (require 'cl)) | ||
| 37 | (require 'pcvs-util) | ||
| 38 | |||
| 39 | ;;; | ||
| 40 | |||
| 41 | (defgroup cvs-status nil | ||
| 42 | "Major mode for browsing `cvs status' output." | ||
| 43 | :group 'pcl-cvs | ||
| 44 | :prefix "cvs-status-") | ||
| 45 | |||
| 46 | (easy-mmode-defmap cvs-status-mode-map | ||
| 47 | '(("n" . next-line) | ||
| 48 | ("N" . cvs-status-next-entry) | ||
| 49 | ("\M-n" . cvs-status-next-entry) | ||
| 50 | ("p" . previous-line) | ||
| 51 | ("P" . cvs-status-prev-entry) | ||
| 52 | ("\M-p" . cvs-status-prev-entry) | ||
| 53 | ("t" . cvs-status-cvstrees) | ||
| 54 | ("T" . cvs-status-trees)) | ||
| 55 | "CVS-Status' keymap." | ||
| 56 | :group 'cvs-status | ||
| 57 | :inherit 'cvs-mode-map) | ||
| 58 | |||
| 59 | ;;(easy-menu-define cvs-status-menu cvs-status-mode-map | ||
| 60 | ;; "Menu for `cvs-status-mode'." | ||
| 61 | ;; '("CVS-Status" | ||
| 62 | ;; ["Show Tag Trees" cvs-status-tree t] | ||
| 63 | ;; )) | ||
| 64 | |||
| 65 | (defvar cvs-status-mode-hook nil | ||
| 66 | "Hook run at the end of `cvs-status-mode'.") | ||
| 67 | |||
| 68 | (defconst cvs-status-tags-leader-re "^ Existing Tags:$") | ||
| 69 | (defconst cvs-status-entry-leader-re "^File: \\(\\S-+\\)\\s-+Status: \\(.+\\)$") | ||
| 70 | (defconst cvs-status-dir-re "^cvs[.ex]* [a-z]+: Examining \\(.+\\)$") | ||
| 71 | (defconst cvs-status-rev-re "[0-9][.0-9]*\\.[.0-9]*[0-9]") | ||
| 72 | (defconst cvs-status-tag-re "[ \t]\\([a-zA-Z][^ \t\n.]*\\)") | ||
| 73 | |||
| 74 | (defconst cvs-status-font-lock-keywords | ||
| 75 | `((,cvs-status-entry-leader-re | ||
| 76 | (1 'cvs-filename-face) | ||
| 77 | (2 'cvs-need-action-face)) | ||
| 78 | (,cvs-status-tags-leader-re | ||
| 79 | (,cvs-status-rev-re | ||
| 80 | (save-excursion (re-search-forward "^\n" nil 'move) (point)) | ||
| 81 | (progn (re-search-backward cvs-status-tags-leader-re nil t) | ||
| 82 | (forward-line 1)) | ||
| 83 | (0 font-lock-comment-face)) | ||
| 84 | (,cvs-status-tag-re | ||
| 85 | (save-excursion (re-search-forward "^\n" nil 'move) (point)) | ||
| 86 | (progn (re-search-backward cvs-status-tags-leader-re nil t) | ||
| 87 | (forward-line 1)) | ||
| 88 | (1 font-lock-function-name-face))))) | ||
| 89 | (defconst cvs-status-font-lock-defaults | ||
| 90 | '(cvs-status-font-lock-keywords t nil nil nil)) | ||
| 91 | |||
| 92 | |||
| 93 | (put 'cvs-status-mode 'mode-class 'special) | ||
| 94 | ;;;###autoload | ||
| 95 | (autoload 'cvs-status-mode "cvs-status" "Mode used for cvs status output." t) | ||
| 96 | (eval-when-compile (autoload 'easy-mmode-define-derived-mode "easy-mmode")) | ||
| 97 | (easy-mmode-define-derived-mode cvs-status-mode fundamental-mode "CVS-Status" | ||
| 98 | "Mode used for cvs status output." | ||
| 99 | (set (make-local-variable 'font-lock-defaults) cvs-status-font-lock-defaults) | ||
| 100 | (set (make-local-variable 'cvs-minor-wrap-function) 'cvs-status-minor-wrap)) | ||
| 101 | |||
| 102 | |||
| 103 | (defun cvs-status-next-entry (n) | ||
| 104 | "Go to the N'th next cvs status entry." | ||
| 105 | (interactive "p") | ||
| 106 | (if (< n 0) (cvs-status-prev-entry (- n)) | ||
| 107 | (forward-line 1) | ||
| 108 | (re-search-forward cvs-status-entry-leader-re nil t n) | ||
| 109 | (beginning-of-line))) | ||
| 110 | |||
| 111 | (defun cvs-status-prev-entry (n) | ||
| 112 | "Go to the N'th previous cvs status entry." | ||
| 113 | (interactive "p") | ||
| 114 | (if (< n 0) (cvs-status-next-entry (- n)) | ||
| 115 | (forward-line -1) | ||
| 116 | (re-search-backward cvs-status-entry-leader-re nil t n) | ||
| 117 | (beginning-of-line))) | ||
| 118 | |||
| 119 | (defun cvs-status-current-file () | ||
| 120 | (save-excursion | ||
| 121 | (forward-line 1) | ||
| 122 | (or (re-search-backward cvs-status-entry-leader-re nil t) | ||
| 123 | (re-search-forward cvs-status-entry-leader-re)) | ||
| 124 | (let* ((file (match-string 1)) | ||
| 125 | (cvsdir (and (re-search-backward cvs-status-dir-re nil t) | ||
| 126 | (match-string 1))) | ||
| 127 | (pcldir (and (re-search-backward cvs-pcl-cvs-dirchange-re nil t) | ||
| 128 | (match-string 1))) | ||
| 129 | (dir "")) | ||
| 130 | (let ((default-directory "")) | ||
| 131 | (when pcldir (setq dir (expand-file-name pcldir dir))) | ||
| 132 | (when cvsdir (setq dir (expand-file-name cvsdir dir))) | ||
| 133 | (expand-file-name file dir))))) | ||
| 134 | |||
| 135 | (defun cvs-status-current-tag () | ||
| 136 | (save-excursion | ||
| 137 | (let ((pt (point)) | ||
| 138 | (col (current-column)) | ||
| 139 | (start (progn (re-search-backward cvs-status-tags-leader-re nil t) (point))) | ||
| 140 | (end (progn (re-search-forward "^$" nil t) (point)))) | ||
| 141 | (when (and (< start pt) (> end pt)) | ||
| 142 | (goto-char pt) | ||
| 143 | (end-of-line) | ||
| 144 | (let ((tag nil) (dist pt) (end (point))) | ||
| 145 | (beginning-of-line) | ||
| 146 | (while (re-search-forward cvs-status-tag-re end t) | ||
| 147 | (let* ((cole (current-column)) | ||
| 148 | (colb (save-excursion | ||
| 149 | (goto-char (match-beginning 1)) (current-column))) | ||
| 150 | (ndist (min (abs (- cole col)) (abs (- colb col))))) | ||
| 151 | (when (< ndist dist) | ||
| 152 | (setq dist ndist) | ||
| 153 | (setq tag (match-string 1))))) | ||
| 154 | tag))))) | ||
| 155 | |||
| 156 | (defun cvs-status-minor-wrap (buf f) | ||
| 157 | (let ((data (with-current-buffer buf | ||
| 158 | (cons | ||
| 159 | (cons (cvs-status-current-file) | ||
| 160 | (cvs-status-current-tag)) | ||
| 161 | (when (ignore-errors (mark)) | ||
| 162 | ;; `mark-active' is not provided by XEmacs :-( | ||
| 163 | (save-excursion | ||
| 164 | (goto-char (mark)) | ||
| 165 | (cons (cvs-status-current-file) | ||
| 166 | (cvs-status-current-tag)))))))) | ||
| 167 | (let ((cvs-branch-prefix (cdar data)) | ||
| 168 | (cvs-secondary-branch-prefix (and (cdar data) (cddr data))) | ||
| 169 | (cvs-minor-current-files | ||
| 170 | (cons (caar data) | ||
| 171 | (when (and (cadr data) (not (equal (caar data) (cadr data)))) | ||
| 172 | (list (cadr data))))) | ||
| 173 | ;; FIXME: I need to force because the fileinfos are UNKNOWN | ||
| 174 | (cvs-force-command "/F")) | ||
| 175 | (funcall f)))) | ||
| 176 | |||
| 177 | ;; | ||
| 178 | ;; Tagelt, tag element | ||
| 179 | ;; | ||
| 180 | |||
| 181 | (defstruct (cvs-tag | ||
| 182 | (:constructor nil) | ||
| 183 | (:constructor cvs-tag-make | ||
| 184 | (vlist &optional name type)) | ||
| 185 | (:conc-name cvs-tag->)) | ||
| 186 | vlist | ||
| 187 | name | ||
| 188 | type) | ||
| 189 | |||
| 190 | (defsubst cvs-status-vl-to-str (vl) (mapconcat 'number-to-string vl ".")) | ||
| 191 | |||
| 192 | (defun cvs-tag->string (tag) | ||
| 193 | (if (stringp tag) tag | ||
| 194 | (let ((name (cvs-tag->name tag)) | ||
| 195 | (vl (cvs-tag->vlist tag))) | ||
| 196 | (if (null name) (cvs-status-vl-to-str vl) | ||
| 197 | (let ((rev (if vl (concat " (" (cvs-status-vl-to-str vl) ")") ""))) | ||
| 198 | (if (consp name) (mapcar (lambda (name) (concat name rev)) name) | ||
| 199 | (concat name rev))))))) | ||
| 200 | |||
| 201 | (defun cvs-tag-compare-1 (vl1 vl2) | ||
| 202 | (cond | ||
| 203 | ((and (null vl1) (null vl2)) 'equal) | ||
| 204 | ((null vl1) 'more2) | ||
| 205 | ((null vl2) 'more1) | ||
| 206 | (t (let ((v1 (car vl1)) | ||
| 207 | (v2 (car vl2))) | ||
| 208 | (cond | ||
| 209 | ((> v1 v2) 'more1) | ||
| 210 | ((< v1 v2) 'more2) | ||
| 211 | (t (cvs-tag-compare-1 (cdr vl1) (cdr vl2)))))))) | ||
| 212 | |||
| 213 | (defsubst cvs-tag-compare (tag1 tag2) | ||
| 214 | (cvs-tag-compare-1 (cvs-tag->vlist tag1) (cvs-tag->vlist tag2))) | ||
| 215 | |||
| 216 | (defun cvs-tag-merge (tag1 tag2) | ||
| 217 | "Merge TAG1 and TAG2 into one." | ||
| 218 | (let ((type1 (cvs-tag->type tag1)) | ||
| 219 | (type2 (cvs-tag->type tag2)) | ||
| 220 | (name1 (cvs-tag->name tag1)) | ||
| 221 | (name2 (cvs-tag->name tag2))) | ||
| 222 | (unless (equal (cvs-tag->vlist tag1) (cvs-tag->vlist tag2)) | ||
| 223 | (setf (cvs-tag->vlist tag1) nil)) | ||
| 224 | (if type1 | ||
| 225 | (unless (or (not type2) (equal type1 type2)) | ||
| 226 | (setf (cvs-tag->type tag1) nil)) | ||
| 227 | (setf (cvs-tag->type tag1) type2)) | ||
| 228 | (if name1 | ||
| 229 | (setf (cvs-tag->name tag1) (cvs-append name1 name2)) | ||
| 230 | (setf (cvs-tag->name tag1) name2)) | ||
| 231 | tag1)) | ||
| 232 | |||
| 233 | (defun cvs-tree-print (tags printer column) | ||
| 234 | "Print the tree of TAGS where each tag's string is given by PRINTER. | ||
| 235 | PRINTER should accept both a tag (in which case it should return a string) | ||
| 236 | or a string (in which case it should simply return its argument). | ||
| 237 | A tag cannot be a CONS. The return value can also be a list of strings, | ||
| 238 | if several nodes where merged into one. | ||
| 239 | The tree will be printed no closer than column COLUMN." | ||
| 240 | |||
| 241 | (let* ((eol (save-excursion (end-of-line) (current-column))) | ||
| 242 | (column (max (+ eol 2) column))) | ||
| 243 | (if (null tags) column | ||
| 244 | ;;(move-to-column-force column) | ||
| 245 | (let* ((rev (cvs-car tags)) | ||
| 246 | (name (funcall printer (cvs-car rev))) | ||
| 247 | (rest (append (cvs-cdr name) (cvs-cdr tags))) | ||
| 248 | (prefix | ||
| 249 | (save-excursion | ||
| 250 | (or (= (forward-line 1) 0) (insert "\n")) | ||
| 251 | (cvs-tree-print rest printer column)))) | ||
| 252 | (assert (>= prefix column)) | ||
| 253 | (move-to-column prefix t) | ||
| 254 | (assert (eolp)) | ||
| 255 | (insert (cvs-car name)) | ||
| 256 | (dolist (br (cvs-cdr rev)) | ||
| 257 | (let* ((column (current-column)) | ||
| 258 | (brrev (funcall printer (cvs-car br))) | ||
| 259 | (brlength (length (cvs-car brrev))) | ||
| 260 | (brfill (concat (make-string (/ brlength 2) ? ) "|")) | ||
| 261 | (prefix | ||
| 262 | (save-excursion | ||
| 263 | (insert " -- ") | ||
| 264 | (cvs-tree-print (cvs-append brrev brfill (cvs-cdr br)) | ||
| 265 | printer (current-column))))) | ||
| 266 | (delete-region (save-excursion (move-to-column prefix) (point)) | ||
| 267 | (point)) | ||
| 268 | (insert " " (make-string (- prefix column 2) ?-) " ") | ||
| 269 | (end-of-line))) | ||
| 270 | prefix)))) | ||
| 271 | |||
| 272 | (defun cvs-tree-merge (tree1 tree2) | ||
| 273 | "Merge tags trees TREE1 and TREE2 into one. | ||
| 274 | BEWARE: because of stability issues, this is not a symetric operation." | ||
| 275 | (assert (and (listp tree1) (listp tree2))) | ||
| 276 | (cond | ||
| 277 | ((null tree1) tree2) | ||
| 278 | ((null tree2) tree1) | ||
| 279 | (t | ||
| 280 | (let* ((rev1 (car tree1)) | ||
| 281 | (tag1 (cvs-car rev1)) | ||
| 282 | (vl1 (cvs-tag->vlist tag1)) | ||
| 283 | (l1 (length vl1)) | ||
| 284 | (rev2 (car tree2)) | ||
| 285 | (tag2 (cvs-car rev2)) | ||
| 286 | (vl2 (cvs-tag->vlist tag2)) | ||
| 287 | (l2 (length vl2))) | ||
| 288 | (cond | ||
| 289 | ((= l1 l2) | ||
| 290 | (case (cvs-tag-compare tag1 tag2) | ||
| 291 | (more1 (list* rev2 (cvs-tree-merge tree1 (cdr tree2)))) | ||
| 292 | (more2 (list* rev1 (cvs-tree-merge (cdr tree1) tree2))) | ||
| 293 | (equal | ||
| 294 | (cons (cons (cvs-tag-merge tag1 tag2) | ||
| 295 | (cvs-tree-merge (cvs-cdr rev1) (cvs-cdr rev2))) | ||
| 296 | (cvs-tree-merge (cdr tree1) (cdr tree2)))))) | ||
| 297 | ((> l1 l2) | ||
| 298 | (cvs-tree-merge (list (cons (cvs-tag-make (butlast vl1)) tree1)) tree2)) | ||
| 299 | ((< l1 l2) | ||
| 300 | (cvs-tree-merge tree1 (list (cons (cvs-tag-make (butlast vl2)) tree2))))))))) | ||
| 301 | |||
| 302 | (defun cvs-tag-make-tag (tag) | ||
| 303 | (let ((vl (mapcar 'string-to-number (split-string (third tag) "\\.")))) | ||
| 304 | (cvs-tag-make vl (first tag) (intern (second tag))))) | ||
| 305 | |||
| 306 | (defun cvs-tags->tree (tags) | ||
| 307 | "Make a tree out of a list of TAGS." | ||
| 308 | (let ((tags | ||
| 309 | (mapcar (lambda (tag) | ||
| 310 | (let ((tag (cvs-tag-make-tag tag))) | ||
| 311 | (list (if (not (eq (cvs-tag->type tag) 'branch)) tag | ||
| 312 | (list (cvs-tag-make (butlast (cvs-tag->vlist tag))) | ||
| 313 | tag))))) | ||
| 314 | tags))) | ||
| 315 | (while (cdr tags) | ||
| 316 | (let (tl) | ||
| 317 | (while tags | ||
| 318 | (push (cvs-tree-merge (pop tags) (pop tags)) tl)) | ||
| 319 | (setq tags (nreverse tl)))) | ||
| 320 | (car tags))) | ||
| 321 | |||
| 322 | (defun cvs-status-get-tags () | ||
| 323 | "Look for a list of tags, read them in and delete them. | ||
| 324 | Returns NIL if there was an empty list of tags and T if there wasn't | ||
| 325 | even a list. Else, return the list of tags where each element of | ||
| 326 | the list is a three-string list TAG, KIND, REV." | ||
| 327 | (let ((tags nil)) | ||
| 328 | (if (not (re-search-forward cvs-status-tags-leader-re nil t)) t | ||
| 329 | (forward-char 1) | ||
| 330 | (let ((pt (point)) | ||
| 331 | (lastrev nil) | ||
| 332 | (case-fold-search t)) | ||
| 333 | (or | ||
| 334 | (looking-at "\\s-+no\\s-+tags") | ||
| 335 | |||
| 336 | (progn ; normal listing | ||
| 337 | (while (looking-at "^[ \t]+\\([^ \t\n]+\\)[ \t]+(\\([a-z]+\\): \\(.+\\))$") | ||
| 338 | (push (list (match-string 1) (match-string 2) (match-string 3)) tags) | ||
| 339 | (forward-line 1)) | ||
| 340 | (unless (looking-at "^$") (setq tags nil) (goto-char pt)) | ||
| 341 | tags) | ||
| 342 | |||
| 343 | (progn ; cvstree-style listing | ||
| 344 | (while (or (looking-at "^ .+\\(.\\) \\([0-9.]+\\): \\([^\n\t .0-9][^\n\t ]*\\)?$") | ||
| 345 | (and lastrev | ||
| 346 | (looking-at "^ .+\\(\\) \\(8\\)? \\([^\n\t .0-9][^\n\t ]*\\)$"))) | ||
| 347 | (setq lastrev (or (match-string 2) lastrev)) | ||
| 348 | (push (list (match-string 3) | ||
| 349 | (if (equal (match-string 1) " ") "branch" "revision") | ||
| 350 | lastrev) tags) | ||
| 351 | (forward-line 1)) | ||
| 352 | (unless (looking-at "^$") (setq tags nil) (goto-char pt)) | ||
| 353 | (setq tags (nreverse tags))) | ||
| 354 | |||
| 355 | (progn ; new tree style listing | ||
| 356 | (let* ((re-lead "[ \t]*\\(-+\\)?\\(|\n?[ \t]+\\)?") | ||
| 357 | (re3 (concat re-lead "\\(\\.\\)?\\(" cvs-status-rev-re "\\)")) | ||
| 358 | (re2 (concat re-lead cvs-status-tag-re "\\(\\)")) | ||
| 359 | (re1 (concat re-lead cvs-status-tag-re | ||
| 360 | " (\\(" cvs-status-rev-re "\\))"))) | ||
| 361 | (while (or (looking-at re1) (looking-at re2) (looking-at re3)) | ||
| 362 | (push (list (match-string 3) | ||
| 363 | (if (match-string 1) "branch" "revision") | ||
| 364 | (match-string 4)) tags) | ||
| 365 | (goto-char (match-end 0)) | ||
| 366 | (when (eolp) (forward-char 1)))) | ||
| 367 | (unless (looking-at "^$") (setq tags nil) (goto-char pt)) | ||
| 368 | (setq tags (nreverse tags)))) | ||
| 369 | |||
| 370 | (delete-region pt (point))) | ||
| 371 | tags))) | ||
| 372 | |||
| 373 | (defvar font-lock-mode) | ||
| 374 | (defun cvs-refontify (beg end) | ||
| 375 | (when (and (boundp 'font-lock-mode) | ||
| 376 | font-lock-mode | ||
| 377 | (fboundp 'font-lock-fontify-region)) | ||
| 378 | (font-lock-fontify-region (1- beg) (1+ end)))) | ||
| 379 | |||
| 380 | (defun cvs-status-trees () | ||
| 381 | "Look for a lists of tags, and replace them with trees." | ||
| 382 | (interactive) | ||
| 383 | (save-excursion | ||
| 384 | (goto-char (point-min)) | ||
| 385 | (let ((inhibit-read-only t) | ||
| 386 | (tags nil)) | ||
| 387 | (while (listp (setq tags (cvs-status-get-tags))) | ||
| 388 | ;;(let ((pt (save-excursion (forward-line -1) (point)))) | ||
| 389 | (save-restriction | ||
| 390 | (narrow-to-region (point) (point)) | ||
| 391 | ;;(newline) | ||
| 392 | (cvs-tree-print (cvs-tags->tree tags) 'cvs-tag->string 3)) | ||
| 393 | ;;(cvs-refontify pt (point)) | ||
| 394 | (sit-for 0) | ||
| 395 | ;;) | ||
| 396 | )))) | ||
| 397 | |||
| 398 | ;;;; | ||
| 399 | ;;;; CVSTree-style trees | ||
| 400 | ;;;; | ||
| 401 | |||
| 402 | ;; chars sets. Ripped from cvstree | ||
| 403 | (defvar cvstree-dstr-2byte-ready | ||
| 404 | (when (featurep 'mule) | ||
| 405 | (if (boundp 'current-language-environment) | ||
| 406 | (string= current-language-environment "Japanese") | ||
| 407 | t)) ; mule/emacs-19 | ||
| 408 | "*Variable that specifies characters set used in cvstree tree graph. | ||
| 409 | If non-nil, 2byte (Japanese?) characters set is used. | ||
| 410 | If nil, 1byte characters set is used. | ||
| 411 | 2byte characters might be available with Mule or Emacs with Mule extension.") | ||
| 412 | |||
| 413 | (defconst cvstree-dstr-char-space | ||
| 414 | (if cvstree-dstr-2byte-ready "$B!!(B" " ")) | ||
| 415 | (defconst cvstree-dstr-char-hbar | ||
| 416 | (if cvstree-dstr-2byte-ready "$B(,(B" "-")) | ||
| 417 | (defconst cvstree-dstr-char-vbar | ||
| 418 | (if cvstree-dstr-2byte-ready "$B(-(B" "|")) | ||
| 419 | (defconst cvstree-dstr-char-branch | ||
| 420 | (if cvstree-dstr-2byte-ready "$B(2(B" "+")) | ||
| 421 | (defconst cvstree-dstr-char-eob ;end of branch | ||
| 422 | (if cvstree-dstr-2byte-ready "$B(1(B" "`")) | ||
| 423 | (defconst cvstree-dstr-char-bob ;beginning of branch | ||
| 424 | (if cvstree-dstr-2byte-ready "$B(3(B" "+")) | ||
| 425 | |||
| 426 | (defun cvs-tag-lessp (tag1 tag2) | ||
| 427 | (eq (cvs-tag-compare tag1 tag2) 'more2)) | ||
| 428 | |||
| 429 | (defvar cvs-tree-nomerge t) | ||
| 430 | |||
| 431 | (defun cvs-status-cvstrees (&optional arg) | ||
| 432 | "Look for a list of tags, and replace it with a tree. | ||
| 433 | Optional prefix ARG chooses between two representations." | ||
| 434 | (interactive "P") | ||
| 435 | (save-excursion | ||
| 436 | (goto-char (point-min)) | ||
| 437 | (let ((inhibit-read-only t) | ||
| 438 | (tags nil) | ||
| 439 | (cvs-tree-nomerge (if arg (not cvs-tree-nomerge) cvs-tree-nomerge))) | ||
| 440 | (while (listp (setq tags (cvs-status-get-tags))) | ||
| 441 | (let ((tags (mapcar 'cvs-tag-make-tag tags)) | ||
| 442 | ;;(pt (save-excursion (forward-line -1) (point))) | ||
| 443 | ) | ||
| 444 | (setq tags (sort tags 'cvs-tag-lessp)) | ||
| 445 | (let* ((first (first tags)) | ||
| 446 | (prev (if (cvs-tag-p first) | ||
| 447 | (list (first (cvs-tag->vlist first))) nil))) | ||
| 448 | (cvs-tree-tags-insert tags prev) | ||
| 449 | ;;(cvs-refontify pt (point)) | ||
| 450 | (sit-for 0))))))) | ||
| 451 | |||
| 452 | (defun cvs-tree-tags-insert (tags prev) | ||
| 453 | (when tags | ||
| 454 | (let* ((tag (car tags)) | ||
| 455 | (vlist (cvs-tag->vlist tag)) | ||
| 456 | (nprev ;"next prev" | ||
| 457 | (let* ((next (cvs-car (cadr tags))) | ||
| 458 | (nprev (if (and cvs-tree-nomerge next | ||
| 459 | (equal vlist (cvs-tag->vlist next))) | ||
| 460 | prev vlist))) | ||
| 461 | (cvs-map (lambda (v p) v) nprev prev))) | ||
| 462 | (after (save-excursion | ||
| 463 | (newline) | ||
| 464 | (cvs-tree-tags-insert (cdr tags) nprev))) | ||
| 465 | (pe t) ;"prev equal" | ||
| 466 | (nas nil)) ;"next afters" to be returned | ||
| 467 | (insert " ") | ||
| 468 | (do* ((vs vlist (cdr vs)) | ||
| 469 | (ps prev (cdr ps)) | ||
| 470 | (as after (cdr as))) | ||
| 471 | ((and (null as) (null vs) (null ps)) | ||
| 472 | (let ((revname (cvs-status-vl-to-str vlist))) | ||
| 473 | (if (cvs-every 'identity (cvs-map 'equal prev vlist)) | ||
| 474 | (insert (make-string (+ 4 (length revname)) ? ) | ||
| 475 | (or (cvs-tag->name tag) "")) | ||
| 476 | (insert " " revname ": " (or (cvs-tag->name tag) ""))))) | ||
| 477 | (let* ((eq (and pe (equal (car ps) (car vs)))) | ||
| 478 | (next-eq (equal (cadr ps) (cadr vs)))) | ||
| 479 | (let* ((na+char | ||
| 480 | (if (car as) | ||
| 481 | (if eq | ||
| 482 | (if next-eq (cons t cvstree-dstr-char-vbar) | ||
| 483 | (cons t cvstree-dstr-char-branch)) | ||
| 484 | (cons nil cvstree-dstr-char-bob)) | ||
| 485 | (if eq | ||
| 486 | (if next-eq (cons nil cvstree-dstr-char-space) | ||
| 487 | (cons t cvstree-dstr-char-eob)) | ||
| 488 | (cons nil (if (and (eq (cvs-tag->type tag) 'branch) | ||
| 489 | (cvs-every 'null as)) | ||
| 490 | cvstree-dstr-char-space | ||
| 491 | cvstree-dstr-char-hbar)))))) | ||
| 492 | (insert (cdr na+char)) | ||
| 493 | (push (car na+char) nas)) | ||
| 494 | (setq pe eq))) | ||
| 495 | (nreverse nas)))) | ||
| 496 | |||
| 497 | ;;;; | ||
| 498 | ;;;; Merged trees from different files | ||
| 499 | ;;;; | ||
| 500 | |||
| 501 | (defun cvs-tree-fuzzy-merge-1 (trees tree prev) | ||
| 502 | ) | ||
| 503 | |||
| 504 | (defun cvs-tree-fuzzy-merge (trees tree) | ||
| 505 | "Do the impossible: merge TREE into TREES." | ||
| 506 | ()) | ||
| 507 | |||
| 508 | (defun cvs-tree () | ||
| 509 | "Get tags from the status output and merge tham all into a big tree." | ||
| 510 | (save-excursion | ||
| 511 | (goto-char (point-min)) | ||
| 512 | (let ((inhibit-read-only t) | ||
| 513 | (trees (make-vector 31 0)) tree) | ||
| 514 | (while (listp (setq tree (cvs-tags->tree (cvs-status-get-tags)))) | ||
| 515 | (cvs-tree-fuzzy-merge trees tree)) | ||
| 516 | (erase-buffer) | ||
| 517 | (let ((cvs-tag-print-rev nil)) | ||
| 518 | (cvs-tree-print tree 'cvs-tag->string 3))))) | ||
| 519 | |||
| 520 | |||
| 521 | (provide 'cvs-status) | ||
| 522 | |||
| 523 | ;;; cvs-status.el ends here | ||
diff --git a/lisp/emacs-lisp/ewoc.el b/lisp/emacs-lisp/ewoc.el new file mode 100644 index 00000000000..2af8dd49298 --- /dev/null +++ b/lisp/emacs-lisp/ewoc.el | |||
| @@ -0,0 +1,620 @@ | |||
| 1 | ;;; ewoc.el -- Utility to maintain a view of a list of objects in a buffer | ||
| 2 | |||
| 3 | ;; Copyright (C) 1991-2000 Free Software Foundation | ||
| 4 | |||
| 5 | ;; Author: Per Cederqvist <ceder@lysator.liu.se> | ||
| 6 | ;; Inge Wallin <inge@lysator.liu.se> | ||
| 7 | ;; Maintainer: monnier@gnu.org | ||
| 8 | ;; Created: 3 Aug 1992 | ||
| 9 | ;; Keywords: extensions, lisp | ||
| 10 | |||
| 11 | ;; This file is part of GNU Emacs. | ||
| 12 | |||
| 13 | ;; GNU Emacs is free software; you can redistribute it and/or modify | ||
| 14 | ;; it under the terms of the GNU General Public License as published by | ||
| 15 | ;; the Free Software Foundation; either version 2, or (at your option) | ||
| 16 | ;; any later version. | ||
| 17 | |||
| 18 | ;; GNU Emacs is distributed in the hope that it will be useful, | ||
| 19 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 20 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 21 | ;; GNU General Public License for more details. | ||
| 22 | |||
| 23 | ;; You should have received a copy of the GNU General Public License | ||
| 24 | ;; along with GNU Emacs; see the file COPYING. If not, write to the | ||
| 25 | ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, | ||
| 26 | ;; Boston, MA 02111-1307, USA. | ||
| 27 | |||
| 28 | ;;; Commentary: | ||
| 29 | |||
| 30 | ;; Ewoc Was Once Cookie | ||
| 31 | ;; But now it's Emacs' Widget for Object Collections | ||
| 32 | |||
| 33 | ;; As the name implies this derives from the `cookie' package (part | ||
| 34 | ;; of Elib). The changes are mostly superficial: | ||
| 35 | |||
| 36 | ;; - uses CL (and its `defstruct' | ||
| 37 | ;; - separate from Elib. | ||
| 38 | ;; - uses its own version of a doubly-linked list which allows us | ||
| 39 | ;; to merge the elib-wrapper and the elib-node structures into ewoc-node | ||
| 40 | ;; - dropping functions not used by PCL-CVS (the only client of ewoc at the | ||
| 41 | ;; time of writing) | ||
| 42 | ;; - removing unused arguments | ||
| 43 | ;; - renaming: | ||
| 44 | ;; elib-node ==> ewoc--node | ||
| 45 | ;; collection ==> ewoc | ||
| 46 | ;; tin ==> ewoc--node | ||
| 47 | ;; cookie ==> data or element or elem | ||
| 48 | |||
| 49 | ;; Introduction | ||
| 50 | ;; ============ | ||
| 51 | ;; | ||
| 52 | ;; Ewoc is a package that implements a connection between an | ||
| 53 | ;; dll (a doubly linked list) and the contents of a buffer. | ||
| 54 | ;; Possible uses are dired (have all files in a list, and show them), | ||
| 55 | ;; buffer-list, kom-prioritize (in the LysKOM elisp client) and | ||
| 56 | ;; others. pcl-cvs.el uses ewoc.el. | ||
| 57 | ;; | ||
| 58 | ;; Ewoc can be considered as the `view' part of a model-view-controller. | ||
| 59 | ;; | ||
| 60 | ;; A `element' can be any lisp object. When you use the ewoc | ||
| 61 | ;; package you specify a pretty-printer, a function that inserts | ||
| 62 | ;; a printable representation of the element in the buffer. (The | ||
| 63 | ;; pretty-printer should use "insert" and not | ||
| 64 | ;; "insert-before-markers"). | ||
| 65 | ;; | ||
| 66 | ;; A `ewoc' consists of a doubly linked list of elements, a | ||
| 67 | ;; header, a footer and a pretty-printer. It is displayed at a | ||
| 68 | ;; certain point in a certain buffer. (The buffer and point are | ||
| 69 | ;; fixed when the ewoc is created). The header and the footer | ||
| 70 | ;; are constant strings. They appear before and after the elements. | ||
| 71 | ;; (Currently, once set, they can not be changed). | ||
| 72 | ;; | ||
| 73 | ;; Ewoc does not affect the mode of the buffer in any way. It | ||
| 74 | ;; merely makes it easy to connect an underlying data representation | ||
| 75 | ;; to the buffer contents. | ||
| 76 | ;; | ||
| 77 | ;; A `ewoc--node' is an object that contains one element. There are | ||
| 78 | ;; functions in this package that given an ewoc--node extracts the data, or | ||
| 79 | ;; gives the next or previous ewoc--node. (All ewoc--nodes are linked together | ||
| 80 | ;; in a doubly linked list. The 'previous' ewoc--node is the one that appears | ||
| 81 | ;; before the other in the buffer.) You should not do anything with | ||
| 82 | ;; an ewoc--node except pass it to the functions in this package. | ||
| 83 | ;; | ||
| 84 | ;; An ewoc is a very dynamic thing. You can easily add or delete elements. | ||
| 85 | ;; You can apply a function to all elements in an ewoc, etc, etc. | ||
| 86 | ;; | ||
| 87 | ;; Remember that an element can be anything. Your imagination is the | ||
| 88 | ;; limit! It is even possible to have another ewoc as an | ||
| 89 | ;; element. In that way some kind of tree hierarchy can be created. | ||
| 90 | ;; | ||
| 91 | ;; Full documentation will, God willing, soon be available in a | ||
| 92 | ;; Texinfo manual. | ||
| 93 | |||
| 94 | ;; In the mean time `grep '^(.*ewoc-[^-]' emacs-lisp/ewoc.el' can help | ||
| 95 | ;; you find all the exported functions: | ||
| 96 | ;; | ||
| 97 | ;; (defun ewoc-create (buffer pretty-printer &optional header footer pos) | ||
| 98 | ;; (defalias 'ewoc-data 'ewoc--node-data) | ||
| 99 | ;; (defun ewoc-enter-first (ewoc data) | ||
| 100 | ;; (defun ewoc-enter-last (ewoc data) | ||
| 101 | ;; (defun ewoc-enter-after (ewoc node data) | ||
| 102 | ;; (defun ewoc-enter-before (ewoc node data) | ||
| 103 | ;; (defun ewoc-next (ewoc node) | ||
| 104 | ;; (defun ewoc-prev (ewoc node) | ||
| 105 | ;; (defun ewoc-nth (ewoc n) | ||
| 106 | ;; (defun ewoc-map (map-function ewoc &rest args) | ||
| 107 | ;; (defun ewoc-filter (ewoc predicate &rest args) | ||
| 108 | ;; (defun ewoc-locate (ewoc pos &optional guess) | ||
| 109 | ;; (defun ewoc-invalidate (ewoc &rest nodes) | ||
| 110 | ;; (defun ewoc-goto-prev (ewoc pos arg) | ||
| 111 | ;; (defun ewoc-goto-next (ewoc pos arg) | ||
| 112 | ;; (defun ewoc-goto-node (ewoc node) | ||
| 113 | ;; (defun ewoc-refresh (ewoc) | ||
| 114 | ;; (defun ewoc-collect (ewoc predicate &rest args) | ||
| 115 | ;; (defun ewoc-buffer (ewoc) | ||
| 116 | |||
| 117 | |||
| 118 | ;; Coding conventions | ||
| 119 | ;; ================== | ||
| 120 | ;; | ||
| 121 | ;; All functions of course start with `ewoc'. Functions and macros | ||
| 122 | ;; starting with the prefix `ewoc--' are meant for internal use, | ||
| 123 | ;; while those starting with `ewoc-' are exported for public use. | ||
| 124 | ;; There are currently no global or buffer-local variables used. | ||
| 125 | |||
| 126 | |||
| 127 | ;;; Code: | ||
| 128 | |||
| 129 | (eval-when-compile (require 'cl)) ;because of CL compiler macros | ||
| 130 | |||
| 131 | ;; The doubly linked list is implemented as a circular list | ||
| 132 | ;; with a dummy node first and last. The dummy node is used as | ||
| 133 | ;; "the dll" (or rather is the dll handle passed around). | ||
| 134 | |||
| 135 | (defstruct (ewoc--node | ||
| 136 | (:type vector) ;required for ewoc--node-branch hack | ||
| 137 | (:constructor ewoc--node-create (start-marker data))) | ||
| 138 | left right data start-marker) | ||
| 139 | |||
| 140 | (defalias 'ewoc--node-branch 'aref) | ||
| 141 | |||
| 142 | (defun ewoc--dll-create () | ||
| 143 | "Create an empty doubly linked list." | ||
| 144 | (let ((dummy-node (ewoc--node-create 'DL-LIST 'DL-LIST))) | ||
| 145 | (setf (ewoc--node-right dummy-node) dummy-node) | ||
| 146 | (setf (ewoc--node-left dummy-node) dummy-node) | ||
| 147 | dummy-node)) | ||
| 148 | |||
| 149 | (defun ewoc--node-enter-before (node elemnode) | ||
| 150 | "Insert ELEMNODE before NODE in a DLL." | ||
| 151 | (assert (and (null (ewoc--node-left elemnode)) (null (ewoc--node-right elemnode)))) | ||
| 152 | (setf (ewoc--node-left elemnode) (ewoc--node-left node)) | ||
| 153 | (setf (ewoc--node-right elemnode) node) | ||
| 154 | (setf (ewoc--node-right (ewoc--node-left node)) elemnode) | ||
| 155 | (setf (ewoc--node-left node) elemnode)) | ||
| 156 | |||
| 157 | (defun ewoc--node-enter-first (dll node) | ||
| 158 | "Add a free floating NODE first in DLL." | ||
| 159 | (ewoc--node-enter-before (ewoc--node-right dll) node)) | ||
| 160 | |||
| 161 | (defun ewoc--node-enter-last (dll node) | ||
| 162 | "Add a free floating NODE last in DLL." | ||
| 163 | (ewoc--node-enter-before dll node)) | ||
| 164 | |||
| 165 | (defun ewoc--node-next (dll node) | ||
| 166 | "Return the node after NODE, or nil if NODE is the last node." | ||
| 167 | (unless (eq (ewoc--node-right node) dll) (ewoc--node-right node))) | ||
| 168 | |||
| 169 | (defun ewoc--node-prev (dll node) | ||
| 170 | "Return the node before NODE, or nil if NODE is the first node." | ||
| 171 | (unless (eq (ewoc--node-left node) dll) (ewoc--node-left node))) | ||
| 172 | |||
| 173 | (defun ewoc--node-delete (node) | ||
| 174 | "Unbind NODE from its doubly linked list and return it." | ||
| 175 | ;; This is a no-op when applied to the dummy node. This will return | ||
| 176 | ;; nil if applied to the dummy node since it always contains nil. | ||
| 177 | (setf (ewoc--node-right (ewoc--node-left node)) (ewoc--node-right node)) | ||
| 178 | (setf (ewoc--node-left (ewoc--node-right node)) (ewoc--node-left node)) | ||
| 179 | (setf (ewoc--node-left node) nil) | ||
| 180 | (setf (ewoc--node-right node) nil) | ||
| 181 | node) | ||
| 182 | |||
| 183 | (defun ewoc--node-nth (dll n) | ||
| 184 | "Return the Nth node from the doubly linked list DLL. | ||
| 185 | N counts from zero. If DLL is not that long, nil is returned. | ||
| 186 | If N is negative, return the -(N+1)th last element. | ||
| 187 | Thus, (ewoc--node-nth dll 0) returns the first node, | ||
| 188 | and (ewoc--node-nth dll -1) returns the last node." | ||
| 189 | ;; Branch 0 ("follow left pointer") is used when n is negative. | ||
| 190 | ;; Branch 1 ("follow right pointer") is used otherwise. | ||
| 191 | (let* ((branch (if (< n 0) 0 1)) | ||
| 192 | (node (ewoc--node-branch dll branch))) | ||
| 193 | (if (< n 0) (setq n (- -1 n))) | ||
| 194 | (while (and (not (eq dll node)) (> n 0)) | ||
| 195 | (setq node (ewoc--node-branch node branch)) | ||
| 196 | (setq n (1- n))) | ||
| 197 | (unless (eq dll node) node))) | ||
| 198 | |||
| 199 | |||
| 200 | ;;; The ewoc data type | ||
| 201 | |||
| 202 | (defstruct (ewoc | ||
| 203 | (:constructor nil) | ||
| 204 | (:constructor ewoc--create | ||
| 205 | (buffer pretty-printer header footer dll)) | ||
| 206 | (:conc-name ewoc--)) | ||
| 207 | buffer pretty-printer header footer dll last-node) | ||
| 208 | |||
| 209 | (defmacro ewoc--set-buffer-bind-dll-let* (ewoc varlist &rest forms) | ||
| 210 | "Execute FORMS with ewoc--buffer selected as current buffer, | ||
| 211 | dll bound to ewoc--dll, and VARLIST bound as in a let*. | ||
| 212 | dll will be bound when VARLIST is initialized, but the current | ||
| 213 | buffer will *not* have been changed. | ||
| 214 | Return value of last form in FORMS." | ||
| 215 | (let ((old-buffer (make-symbol "old-buffer")) | ||
| 216 | (hnd (make-symbol "ewoc"))) | ||
| 217 | (` (let* (((, old-buffer) (current-buffer)) | ||
| 218 | ((, hnd) (, ewoc)) | ||
| 219 | (dll (ewoc--dll (, hnd))) | ||
| 220 | (,@ varlist)) | ||
| 221 | (set-buffer (ewoc--buffer (, hnd))) | ||
| 222 | (unwind-protect | ||
| 223 | (progn (,@ forms)) | ||
| 224 | (set-buffer (, old-buffer))))))) | ||
| 225 | |||
| 226 | (defmacro ewoc--set-buffer-bind-dll (ewoc &rest forms) | ||
| 227 | `(ewoc--set-buffer-bind-dll-let* ,ewoc nil ,@forms)) | ||
| 228 | |||
| 229 | (defsubst ewoc--filter-hf-nodes (ewoc node) | ||
| 230 | "Evaluate NODE once and return it. | ||
| 231 | BUT if it is the header or the footer in EWOC return nil instead." | ||
| 232 | (unless (or (eq node (ewoc--header ewoc)) | ||
| 233 | (eq node (ewoc--footer ewoc))) | ||
| 234 | node)) | ||
| 235 | |||
| 236 | |||
| 237 | (defun ewoc--create-special-node (data string pos) | ||
| 238 | "Insert STRING at POS in current buffer. Remember the start | ||
| 239 | position. Create a wrapper containing that start position and the | ||
| 240 | element DATA." | ||
| 241 | (save-excursion | ||
| 242 | ;; Remember the position as a number so that it doesn't move | ||
| 243 | ;; when we insert the string. | ||
| 244 | (when (markerp pos) (setq pos (marker-position pos))) | ||
| 245 | (goto-char pos) | ||
| 246 | (let ((inhibit-read-only t)) | ||
| 247 | ;; Use insert-before-markers so that the marker for the | ||
| 248 | ;; next element is updated. | ||
| 249 | (insert-before-markers string) | ||
| 250 | ;; Always insert a newline. You want invisible elements? You | ||
| 251 | ;; lose. (At least in this version). FIXME-someday. (It is | ||
| 252 | ;; harder to fix than it might seem. All markers have to point | ||
| 253 | ;; to the right place all the time...) | ||
| 254 | (insert-before-markers ?\n) | ||
| 255 | (ewoc--node-create (copy-marker pos) data)))) | ||
| 256 | |||
| 257 | |||
| 258 | (defun ewoc--create-node (data pretty-printer pos) | ||
| 259 | "Call PRETTY-PRINTER with point set at POS in current buffer. | ||
| 260 | Remember the start position. Create a wrapper containing that | ||
| 261 | start position and the element DATA." | ||
| 262 | (save-excursion | ||
| 263 | ;; Remember the position as a number so that it doesn't move | ||
| 264 | ;; when we insert the string. | ||
| 265 | (when (markerp pos) (setq pos (marker-position pos))) | ||
| 266 | (goto-char pos) | ||
| 267 | (let ((inhibit-read-only t)) | ||
| 268 | ;; Insert the trailing newline using insert-before-markers | ||
| 269 | ;; so that the start position for the next element is updated. | ||
| 270 | (insert-before-markers ?\n) | ||
| 271 | ;; Move back, and call the pretty-printer. | ||
| 272 | (backward-char 1) | ||
| 273 | (funcall pretty-printer data) | ||
| 274 | (ewoc--node-create (copy-marker pos) data)))) | ||
| 275 | |||
| 276 | |||
| 277 | (defun ewoc--delete-node-internal (ewoc node) | ||
| 278 | "Delete a data string from EWOC. | ||
| 279 | Can not be used on the footer. Returns the wrapper that is deleted. | ||
| 280 | The start-marker in the wrapper is set to nil, so that it doesn't | ||
| 281 | consume any more resources." | ||
| 282 | (let ((dll (ewoc--dll ewoc)) | ||
| 283 | (inhibit-read-only t)) | ||
| 284 | ;; If we are about to delete the node pointed at by last-node, | ||
| 285 | ;; set last-node to nil. | ||
| 286 | (if (eq (ewoc--last-node ewoc) node) | ||
| 287 | (setf (ewoc--last-node ewoc) nil)) | ||
| 288 | |||
| 289 | (delete-region (ewoc--node-start-marker node) | ||
| 290 | (ewoc--node-start-marker (ewoc--node-next dll node))) | ||
| 291 | (set-marker (ewoc--node-start-marker node) nil) | ||
| 292 | ;; Delete the node, and return the wrapper. | ||
| 293 | (ewoc--node-delete node))) | ||
| 294 | |||
| 295 | |||
| 296 | (defvar dll) ;passed by dynamic binding | ||
| 297 | |||
| 298 | (defun ewoc--refresh-node (ewoc node) | ||
| 299 | "Redisplay the element represented by NODE. | ||
| 300 | Can not be used on the footer. dll *must* be bound to | ||
| 301 | \(ewoc--dll ewoc)." | ||
| 302 | (let ((inhibit-read-only t)) | ||
| 303 | (save-excursion | ||
| 304 | ;; First, remove the string from the buffer: | ||
| 305 | (delete-region (ewoc--node-start-marker node) | ||
| 306 | (1- (marker-position | ||
| 307 | (ewoc--node-start-marker (ewoc--node-next dll node))))) | ||
| 308 | ;; Calculate and insert the string. | ||
| 309 | (goto-char (ewoc--node-start-marker node)) | ||
| 310 | (funcall (ewoc--pretty-printer ewoc) | ||
| 311 | (ewoc--node-data node))))) | ||
| 312 | |||
| 313 | ;;; =========================================================================== | ||
| 314 | ;;; Public members of the Ewoc package | ||
| 315 | |||
| 316 | |||
| 317 | (defun ewoc-create (buffer pretty-printer &optional header footer pos) | ||
| 318 | "Create an empty ewoc. | ||
| 319 | |||
| 320 | The ewoc will be inserted in BUFFER. BUFFER may be a | ||
| 321 | buffer or a buffer name. It is created if it does not exist. | ||
| 322 | |||
| 323 | PRETTY-PRINTER should be a function that takes one argument, an | ||
| 324 | element, and inserts a string representing it in the buffer (at | ||
| 325 | point). The string PRETTY-PRINTER inserts may be empty or span | ||
| 326 | several linse. A trailing newline will always be inserted | ||
| 327 | automatically. The PRETTY-PRINTER should use insert, and not | ||
| 328 | insert-before-markers. | ||
| 329 | |||
| 330 | Optional third argument HEADER is a string that will always be | ||
| 331 | present at the top of the ewoc. HEADER should end with a | ||
| 332 | newline. Optionaly fourth argument FOOTER is similar, and will | ||
| 333 | always be inserted at the bottom of the ewoc. | ||
| 334 | |||
| 335 | Optional fifth argument POS is a buffer position, specifying | ||
| 336 | where the ewoc will be inserted. It defaults to the | ||
| 337 | beginning of the buffer." | ||
| 338 | (let ((new-ewoc | ||
| 339 | (ewoc--create (get-buffer-create buffer) | ||
| 340 | pretty-printer nil nil (ewoc--dll-create)))) | ||
| 341 | (ewoc--set-buffer-bind-dll new-ewoc | ||
| 342 | ;; Set default values | ||
| 343 | (unless header (setq header "")) | ||
| 344 | (unless footer (setq footer "")) | ||
| 345 | (unless pos (setq pos (point-min))) | ||
| 346 | ;; Force header to be above footer. | ||
| 347 | (if (markerp pos) (setq pos (marker-position pos))) | ||
| 348 | (let ((foot (ewoc--create-special-node footer footer pos)) | ||
| 349 | (head (ewoc--create-special-node header header pos))) | ||
| 350 | (ewoc--node-enter-first dll head) | ||
| 351 | (ewoc--node-enter-last dll foot) | ||
| 352 | (setf (ewoc--header new-ewoc) (ewoc--node-nth dll 0)) | ||
| 353 | (setf (ewoc--footer new-ewoc) (ewoc--node-nth dll -1)))) | ||
| 354 | ;; Return the ewoc | ||
| 355 | new-ewoc)) | ||
| 356 | |||
| 357 | (defalias 'ewoc-data 'ewoc--node-data) | ||
| 358 | |||
| 359 | (defun ewoc-enter-first (ewoc data) | ||
| 360 | "Enter DATA first in EWOC." | ||
| 361 | (ewoc--set-buffer-bind-dll ewoc | ||
| 362 | (ewoc-enter-after ewoc (ewoc--node-nth dll 0) data))) | ||
| 363 | |||
| 364 | (defun ewoc-enter-last (ewoc data) | ||
| 365 | "Enter DATA last in EWOC." | ||
| 366 | (ewoc--set-buffer-bind-dll ewoc | ||
| 367 | (ewoc-enter-before ewoc (ewoc--node-nth dll -1) data))) | ||
| 368 | |||
| 369 | |||
| 370 | (defun ewoc-enter-after (ewoc node data) | ||
| 371 | "Enter a new element DATA after NODE in EWOC." | ||
| 372 | (ewoc--set-buffer-bind-dll ewoc | ||
| 373 | (ewoc-enter-before ewoc (ewoc--node-next dll node) data))) | ||
| 374 | |||
| 375 | (defun ewoc-enter-before (ewoc node data) | ||
| 376 | "Enter a new element DATA before NODE in EWOC." | ||
| 377 | (ewoc--set-buffer-bind-dll ewoc | ||
| 378 | (ewoc--node-enter-before | ||
| 379 | node | ||
| 380 | (ewoc--create-node | ||
| 381 | data | ||
| 382 | (ewoc--pretty-printer ewoc) | ||
| 383 | (ewoc--node-start-marker node))))) | ||
| 384 | |||
| 385 | (defun ewoc-next (ewoc node) | ||
| 386 | "Get the next node. | ||
| 387 | Returns nil if NODE is nil or the last element." | ||
| 388 | (when node | ||
| 389 | (ewoc--filter-hf-nodes | ||
| 390 | ewoc (ewoc--node-next (ewoc--dll ewoc) node)))) | ||
| 391 | |||
| 392 | (defun ewoc-prev (ewoc node) | ||
| 393 | "Get the previous node. | ||
| 394 | Returns nil if NODE is nil or the first element." | ||
| 395 | (when node | ||
| 396 | (ewoc--filter-hf-nodes | ||
| 397 | ewoc | ||
| 398 | (ewoc--node-prev (ewoc--dll ewoc) node)))) | ||
| 399 | |||
| 400 | |||
| 401 | (defun ewoc-nth (ewoc n) | ||
| 402 | "Return the Nth node. | ||
| 403 | N counts from zero. Nil is returned if there is less than N elements. | ||
| 404 | If N is negative, return the -(N+1)th last element. | ||
| 405 | Thus, (ewoc-nth dll 0) returns the first node, | ||
| 406 | and (ewoc-nth dll -1) returns the last node. | ||
| 407 | Use `ewoc--node-data' to extract the data from the node." | ||
| 408 | ;; Skip the header (or footer, if n is negative). | ||
| 409 | (setq n (if (< n 0) (1- n) (1+ n))) | ||
| 410 | (ewoc--filter-hf-nodes ewoc | ||
| 411 | (ewoc--node-nth (ewoc--dll ewoc) n))) | ||
| 412 | |||
| 413 | (defun ewoc-map (map-function ewoc &rest args) | ||
| 414 | "Apply MAP-FUNCTION to all elements in EWOC. | ||
| 415 | MAP-FUNCTION is applied to the first element first. | ||
| 416 | If MAP-FUNCTION returns non-nil the element will be refreshed (its | ||
| 417 | pretty-printer will be called once again). | ||
| 418 | |||
| 419 | Note that the buffer for EWOC will be current buffer when MAP-FUNCTION | ||
| 420 | is called. MAP-FUNCTION must restore the current buffer to BUFFER before | ||
| 421 | it returns, if it changes it. | ||
| 422 | |||
| 423 | If more than two arguments are given, the remaining | ||
| 424 | arguments will be passed to MAP-FUNCTION." | ||
| 425 | (ewoc--set-buffer-bind-dll-let* ewoc | ||
| 426 | ((footer (ewoc--footer ewoc)) | ||
| 427 | (node (ewoc--node-nth dll 1))) | ||
| 428 | (while (not (eq node footer)) | ||
| 429 | (if (apply map-function (ewoc--node-data node) args) | ||
| 430 | (ewoc--refresh-node ewoc node)) | ||
| 431 | (setq node (ewoc--node-next dll node))))) | ||
| 432 | |||
| 433 | (defun ewoc-filter (ewoc predicate &rest args) | ||
| 434 | "Remove all elements in EWOC for which PREDICATE returns nil. | ||
| 435 | Note that the buffer for EWOC will be current-buffer when PREDICATE | ||
| 436 | is called. PREDICATE must restore the current buffer before it returns | ||
| 437 | if it changes it. | ||
| 438 | The PREDICATE is called with the element as its first argument. If any | ||
| 439 | ARGS are given they will be passed to the PREDICATE." | ||
| 440 | (ewoc--set-buffer-bind-dll-let* ewoc | ||
| 441 | ((node (ewoc--node-nth dll 1)) | ||
| 442 | (footer (ewoc--footer ewoc)) | ||
| 443 | (next nil)) | ||
| 444 | (while (not (eq node footer)) | ||
| 445 | (setq next (ewoc--node-next dll node)) | ||
| 446 | (unless (apply predicate (ewoc--node-data node) args) | ||
| 447 | (ewoc--delete-node-internal ewoc node)) | ||
| 448 | (setq node next)))) | ||
| 449 | |||
| 450 | (defun ewoc-locate (ewoc pos &optional guess) | ||
| 451 | "Return the node that POS (a buffer position) is within. | ||
| 452 | POS may be a marker or an integer. | ||
| 453 | GUESS should be a node that it is likely that POS is near. | ||
| 454 | |||
| 455 | If POS points before the first element, the first node is returned. | ||
| 456 | If POS points after the last element, the last node is returned. | ||
| 457 | If the EWOC is empty, nil is returned." | ||
| 458 | (ewoc--set-buffer-bind-dll-let* ewoc | ||
| 459 | ((footer (ewoc--footer ewoc))) | ||
| 460 | |||
| 461 | (cond | ||
| 462 | ;; Nothing present? | ||
| 463 | ((eq (ewoc--node-nth dll 1) (ewoc--node-nth dll -1)) | ||
| 464 | nil) | ||
| 465 | |||
| 466 | ;; Before second elem? | ||
| 467 | ((< pos (ewoc--node-start-marker (ewoc--node-nth dll 2))) | ||
| 468 | (ewoc--node-nth dll 1)) | ||
| 469 | |||
| 470 | ;; After one-before-last elem? | ||
| 471 | ((>= pos (ewoc--node-start-marker (ewoc--node-nth dll -2))) | ||
| 472 | (ewoc--node-nth dll -2)) | ||
| 473 | |||
| 474 | ;; We now know that pos is within a elem. | ||
| 475 | (t | ||
| 476 | ;; Make an educated guess about which of the three known | ||
| 477 | ;; node'es (the first, the last, or GUESS) is nearest. | ||
| 478 | (let* ((best-guess (ewoc--node-nth dll 1)) | ||
| 479 | (distance (abs (- pos (ewoc--node-start-marker best-guess))))) | ||
| 480 | (when guess | ||
| 481 | (let ((d (abs (- pos (ewoc--node-start-marker guess))))) | ||
| 482 | (when (< d distance) | ||
| 483 | (setq distance d) | ||
| 484 | (setq best-guess guess)))) | ||
| 485 | |||
| 486 | (let* ((g (ewoc--node-nth dll -1)) ;Check the last elem | ||
| 487 | (d (abs (- pos (ewoc--node-start-marker g))))) | ||
| 488 | (when (< d distance) | ||
| 489 | (setq distance d) | ||
| 490 | (setq best-guess g))) | ||
| 491 | |||
| 492 | (when (ewoc--last-node ewoc) ;Check "previous". | ||
| 493 | (let* ((g (ewoc--last-node ewoc)) | ||
| 494 | (d (abs (- pos (ewoc--node-start-marker g))))) | ||
| 495 | (when (< d distance) | ||
| 496 | (setq distance d) | ||
| 497 | (setq best-guess g)))) | ||
| 498 | |||
| 499 | ;; best-guess is now a "best guess". | ||
| 500 | ;; Find the correct node. First determine in which direction | ||
| 501 | ;; it lies, and then move in that direction until it is found. | ||
| 502 | |||
| 503 | (cond | ||
| 504 | ;; Is pos after the guess? | ||
| 505 | ((>= pos | ||
| 506 | (ewoc--node-start-marker best-guess)) | ||
| 507 | ;; Loop until we are exactly one node too far down... | ||
| 508 | (while (>= pos (ewoc--node-start-marker best-guess)) | ||
| 509 | (setq best-guess (ewoc--node-next dll best-guess))) | ||
| 510 | ;; ...and return the previous node. | ||
| 511 | (ewoc--node-prev dll best-guess)) | ||
| 512 | |||
| 513 | ;; Pos is before best-guess | ||
| 514 | (t | ||
| 515 | (while (< pos (ewoc--node-start-marker best-guess)) | ||
| 516 | (setq best-guess (ewoc--node-prev dll best-guess))) | ||
| 517 | best-guess))))))) | ||
| 518 | |||
| 519 | (defun ewoc-invalidate (ewoc &rest nodes) | ||
| 520 | "Refresh some elements. | ||
| 521 | The pretty-printer that for EWOC will be called for all NODES." | ||
| 522 | (ewoc--set-buffer-bind-dll ewoc | ||
| 523 | (dolist (node nodes) | ||
| 524 | (ewoc--refresh-node ewoc node)))) | ||
| 525 | |||
| 526 | (defun ewoc-goto-prev (ewoc pos arg) | ||
| 527 | "Move point to the ARGth previous element. | ||
| 528 | Don't move if we are at the first element, or if EWOC is empty. | ||
| 529 | Returns the node we moved to." | ||
| 530 | (ewoc--set-buffer-bind-dll-let* ewoc | ||
| 531 | ((node (ewoc-locate ewoc pos (ewoc--last-node ewoc)))) | ||
| 532 | (when node | ||
| 533 | (while (and node (> arg 0)) | ||
| 534 | (setq arg (1- arg)) | ||
| 535 | (setq node (ewoc--node-prev dll node))) | ||
| 536 | ;; Never step above the first element. | ||
| 537 | (unless (ewoc--filter-hf-nodes ewoc node) | ||
| 538 | (setq node (ewoc--node-nth dll 1))) | ||
| 539 | (ewoc-goto-node ewoc node)))) | ||
| 540 | |||
| 541 | (defun ewoc-goto-next (ewoc pos arg) | ||
| 542 | "Move point to the ARGth next element. | ||
| 543 | Don't move if we are at the last element. | ||
| 544 | Returns the node." | ||
| 545 | (ewoc--set-buffer-bind-dll-let* ewoc | ||
| 546 | ((node (ewoc-locate ewoc pos (ewoc--last-node ewoc)))) | ||
| 547 | (while (and node (> arg 0)) | ||
| 548 | (setq arg (1- arg)) | ||
| 549 | (setq node (ewoc--node-next dll node))) | ||
| 550 | ;; Never step below the first element. | ||
| 551 | (unless (ewoc--filter-hf-nodes ewoc node) | ||
| 552 | (setq node (ewoc--node-nth dll -2))) | ||
| 553 | (ewoc-goto-node ewoc node))) | ||
| 554 | |||
| 555 | (defun ewoc-goto-node (ewoc node) | ||
| 556 | "Move point to NODE." | ||
| 557 | (ewoc--set-buffer-bind-dll ewoc | ||
| 558 | (goto-char (ewoc--node-start-marker node)) | ||
| 559 | (if goal-column (move-to-column goal-column)) | ||
| 560 | (setf (ewoc--last-node ewoc) node))) | ||
| 561 | |||
| 562 | (defun ewoc-refresh (ewoc) | ||
| 563 | "Refresh all data in EWOC. | ||
| 564 | The pretty-printer that was specified when the EWOC was created | ||
| 565 | will be called for all elements in EWOC. | ||
| 566 | Note that `ewoc-invalidate' is more efficient if only a small | ||
| 567 | number of elements needs to be refreshed." | ||
| 568 | (ewoc--set-buffer-bind-dll-let* ewoc | ||
| 569 | ((header (ewoc--header ewoc)) | ||
| 570 | (footer (ewoc--footer ewoc))) | ||
| 571 | (let ((inhibit-read-only t)) | ||
| 572 | (delete-region (ewoc--node-start-marker (ewoc--node-nth dll 1)) | ||
| 573 | (ewoc--node-start-marker footer)) | ||
| 574 | (goto-char (ewoc--node-start-marker footer)) | ||
| 575 | (let ((node (ewoc--node-nth dll 1))) | ||
| 576 | (while (not (eq node footer)) | ||
| 577 | (set-marker (ewoc--node-start-marker node) (point)) | ||
| 578 | (funcall (ewoc--pretty-printer ewoc) | ||
| 579 | (ewoc--node-data node)) | ||
| 580 | (insert "\n") | ||
| 581 | (setq node (ewoc--node-next dll node))))) | ||
| 582 | (set-marker (ewoc--node-start-marker footer) (point)))) | ||
| 583 | |||
| 584 | (defun ewoc-collect (ewoc predicate &rest args) | ||
| 585 | "Select elements from EWOC using PREDICATE. | ||
| 586 | Return a list of all selected data elements. | ||
| 587 | PREDICATE is a function that takes a data element as its first argument. | ||
| 588 | The elements on the returned list will appear in the same order as in | ||
| 589 | the buffer. You should not rely on in which order PREDICATE is | ||
| 590 | called. | ||
| 591 | Note that the buffer the EWOC is displayed in is current-buffer | ||
| 592 | when PREDICATE is called. If PREDICATE must restore current-buffer if | ||
| 593 | it changes it. | ||
| 594 | If more than two arguments are given the | ||
| 595 | remaining arguments will be passed to PREDICATE." | ||
| 596 | (ewoc--set-buffer-bind-dll-let* ewoc | ||
| 597 | ((header (ewoc--header ewoc)) | ||
| 598 | (node (ewoc--node-nth dll -2)) | ||
| 599 | result) | ||
| 600 | (while (not (eq node header)) | ||
| 601 | (if (apply predicate (ewoc--node-data node) args) | ||
| 602 | (push (ewoc--node-data node) result)) | ||
| 603 | (setq node (ewoc--node-prev dll node))) | ||
| 604 | result)) | ||
| 605 | |||
| 606 | (defun ewoc-buffer (ewoc) | ||
| 607 | "Return the buffer that is associated with EWOC. | ||
| 608 | Returns nil if the buffer has been deleted." | ||
| 609 | (let ((buf (ewoc--buffer ewoc))) | ||
| 610 | (when (buffer-name buf) buf))) | ||
| 611 | |||
| 612 | |||
| 613 | (provide 'ewoc) | ||
| 614 | |||
| 615 | ;;; Local Variables: | ||
| 616 | ;;; eval: (put 'ewoc--set-buffer-bind-dll 'lisp-indent-hook 1) | ||
| 617 | ;;; eval: (put 'ewoc--set-buffer-bind-dll-let* 'lisp-indent-hook 2) | ||
| 618 | ;;; End: | ||
| 619 | |||
| 620 | ;;; ewoc.el ends here | ||
diff --git a/lisp/log-edit.el b/lisp/log-edit.el new file mode 100644 index 00000000000..6b238835a9c --- /dev/null +++ b/lisp/log-edit.el | |||
| @@ -0,0 +1,448 @@ | |||
| 1 | ;;; log-edit.el --- Major mode for editing CVS commit messages | ||
| 2 | |||
| 3 | ;; Copyright (C) 1999-2000 Free Software Foundation, Inc. | ||
| 4 | |||
| 5 | ;; Author: Stefan Monnier <monnier@cs.yale.edu> | ||
| 6 | ;; Keywords: pcl-cvs cvs commit log | ||
| 7 | ;; Version: $Name: $ | ||
| 8 | ;; Revision: $Id: log-edit.el,v 1.8 2000/03/05 21:32:21 monnier Exp $ | ||
| 9 | |||
| 10 | ;; This file is part of GNU Emacs. | ||
| 11 | |||
| 12 | ;; GNU Emacs is free software; you can redistribute it and/or modify | ||
| 13 | ;; it under the terms of the GNU General Public License as published by | ||
| 14 | ;; the Free Software Foundation; either version 2, or (at your option) | ||
| 15 | ;; any later version. | ||
| 16 | |||
| 17 | ;; GNU Emacs is distributed in the hope that it will be useful, | ||
| 18 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 19 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 20 | ;; GNU General Public License for more details. | ||
| 21 | |||
| 22 | ;; You should have received a copy of the GNU General Public License | ||
| 23 | ;; along with GNU Emacs; see the file COPYING. If not, write to the | ||
| 24 | ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, | ||
| 25 | ;; Boston, MA 02111-1307, USA. | ||
| 26 | |||
| 27 | ;;; Commentary: | ||
| 28 | |||
| 29 | ;; Todo: | ||
| 30 | |||
| 31 | ;; - Remove a single leading `* <file>' in log-edit-insert-changelog | ||
| 32 | ;; - Move in VC's code | ||
| 33 | ;; - Add compatibility for VC's hook variables | ||
| 34 | ;; - add compatibility with cvs-edit.el | ||
| 35 | |||
| 36 | ;;; Code: | ||
| 37 | |||
| 38 | (eval-when-compile (require 'cl)) | ||
| 39 | (require 'add-log) ; for all the ChangeLog goodies | ||
| 40 | (require 'pcvs-util) | ||
| 41 | (require 'ring) | ||
| 42 | (require 'vc) | ||
| 43 | |||
| 44 | ;;;; | ||
| 45 | ;;;; Global Variables | ||
| 46 | ;;;; | ||
| 47 | |||
| 48 | (defgroup log-edit nil | ||
| 49 | "Major mode for editing commit messages for PCL-CVS." | ||
| 50 | :group 'pcl-cvs | ||
| 51 | :prefix "log-edit-") | ||
| 52 | |||
| 53 | ;; compiler pacifiers | ||
| 54 | (defvar cvs-buffer) | ||
| 55 | |||
| 56 | (easy-mmode-defmap log-edit-mode-map | ||
| 57 | `(("\C-c\C-c" . log-edit-done) | ||
| 58 | ("\C-c\C-a" . log-edit-insert-changelog) | ||
| 59 | ("\C-c\C-f" . log-edit-show-files) | ||
| 60 | ("\C-c?" . log-edit-mode-help)) | ||
| 61 | "Keymap for the `log-edit-mode' (used when editing cvs log messages)." | ||
| 62 | :group 'log-edit | ||
| 63 | :inherit (if (boundp 'vc-log-entry-mode) vc-log-entry-mode)) | ||
| 64 | |||
| 65 | (defcustom log-edit-confirm t | ||
| 66 | "*If non-nil, `log-edit-done' will request confirmation. | ||
| 67 | If 'changed, only request confirmation if the list of files has | ||
| 68 | changed since the beginning of the log-edit session." | ||
| 69 | :group 'log-edit | ||
| 70 | :type '(choice (const changed) (const t) (const nil))) | ||
| 71 | |||
| 72 | (defcustom log-edit-keep-buffer nil | ||
| 73 | "*If non-nil, don't hide the buffer after `log-edit-done'." | ||
| 74 | :group 'log-edit | ||
| 75 | :type 'boolean) | ||
| 76 | |||
| 77 | (defvar cvs-commit-buffer-require-final-newline t | ||
| 78 | "Obsolete, use `log-edit-require-final-newline'.") | ||
| 79 | |||
| 80 | (defcustom log-edit-require-final-newline | ||
| 81 | cvs-commit-buffer-require-final-newline | ||
| 82 | "*Enforce a newline at the end of commit log messages. | ||
| 83 | Enforce it silently if t, query if non-nil and don't do anything if nil." | ||
| 84 | :group 'log-edit | ||
| 85 | :type '(choice (const ask) (const t) (const nil))) | ||
| 86 | |||
| 87 | (defcustom log-edit-setup-invert nil | ||
| 88 | "*Non-nil means `log-edit' should invert the meaning of its SETUP arg. | ||
| 89 | If SETUP is 'force, this variable has no effect." | ||
| 90 | :group 'log-edit | ||
| 91 | :type 'boolean) | ||
| 92 | |||
| 93 | (defcustom log-edit-hook '(log-edit-insert-cvs-template | ||
| 94 | log-edit-insert-changelog) | ||
| 95 | "*Hook run at the end of `log-edit'." | ||
| 96 | :group 'log-edit | ||
| 97 | :type '(hook :options (log-edit-insert-cvs-template | ||
| 98 | log-edit-insert-changelog))) | ||
| 99 | |||
| 100 | (defcustom log-edit-mode-hook nil | ||
| 101 | "*Hook run when entering `log-edit-mode'." | ||
| 102 | :group 'log-edit | ||
| 103 | :type 'hook) | ||
| 104 | |||
| 105 | (defcustom log-edit-done-hook nil | ||
| 106 | "*Hook run before doing the actual commit. | ||
| 107 | This hook can be used to cleanup the message, enforce various | ||
| 108 | conventions, or to allow recording the message in some other database, | ||
| 109 | such as a bug-tracking system. The list of files about to be committed | ||
| 110 | can be obtained from `log-edit-files'." | ||
| 111 | :group 'log-edit | ||
| 112 | :type '(hook :options (log-edit-delete-common-indentation | ||
| 113 | log-edit-add-to-changelog))) | ||
| 114 | |||
| 115 | (defvar cvs-changelog-full-paragraphs t | ||
| 116 | "*If non-nil, include full ChangeLog paragraphs in the CVS log. | ||
| 117 | This may be set in the ``local variables'' section of a ChangeLog, to | ||
| 118 | indicate the policy for that ChangeLog. | ||
| 119 | |||
| 120 | A ChangeLog paragraph is a bunch of log text containing no blank lines; | ||
| 121 | a paragraph usually describes a set of changes with a single purpose, | ||
| 122 | but perhaps spanning several functions in several files. Changes in | ||
| 123 | different paragraphs are unrelated. | ||
| 124 | |||
| 125 | You could argue that the CVS log entry for a file should contain the | ||
| 126 | full ChangeLog paragraph mentioning the change to the file, even though | ||
| 127 | it may mention other files, because that gives you the full context you | ||
| 128 | need to understand the change. This is the behaviour you get when this | ||
| 129 | variable is set to t. | ||
| 130 | |||
| 131 | On the other hand, you could argue that the CVS log entry for a change | ||
| 132 | should contain only the text for the changes which occurred in that | ||
| 133 | file, because the CVS log is per-file. This is the behaviour you get | ||
| 134 | when this variable is set to nil.") | ||
| 135 | |||
| 136 | ;;;; Internal global or buffer-local vars | ||
| 137 | |||
| 138 | (defconst log-edit-files-buf "*log-edit-files*") | ||
| 139 | (defvar log-edit-initial-files nil) | ||
| 140 | (defvar log-edit-callback nil) | ||
| 141 | (defvar log-edit-listfun nil) | ||
| 142 | |||
| 143 | ;;;; | ||
| 144 | ;;;; Actual code | ||
| 145 | ;;;; | ||
| 146 | |||
| 147 | ;;;###autoload | ||
| 148 | (defun log-edit (callback &optional setup listfun &rest ignore) | ||
| 149 | "Setup a buffer to enter a log message. | ||
| 150 | The buffer will be put in `log-edit-mode'. | ||
| 151 | If SETUP is non-nil, the buffer is then erased and `log-edit-hook' is run. | ||
| 152 | Mark and point will be set around the entire contents of the | ||
| 153 | buffer so that it is easy to kill the contents of the buffer with \\[kill-region]. | ||
| 154 | Once you're done editing the message, pressing \\[log-edit-done] will call | ||
| 155 | `log-edit-done' which will end up calling CALLBACK to do the actual commit." | ||
| 156 | (when (and log-edit-setup-invert (not (eq setup 'force))) | ||
| 157 | (setq setup (not setup))) | ||
| 158 | (when setup (erase-buffer)) | ||
| 159 | (log-edit-mode) | ||
| 160 | (set (make-local-variable 'log-edit-callback) callback) | ||
| 161 | (set (make-local-variable 'log-edit-listfun) listfun) | ||
| 162 | (when setup (run-hooks 'log-edit-hook)) | ||
| 163 | (goto-char (point-min)) (push-mark (point-max)) | ||
| 164 | (set (make-local-variable 'log-edit-initial-files) (log-edit-files)) | ||
| 165 | (message (substitute-command-keys | ||
| 166 | "Press \\[log-edit-done] when you are done editing."))) | ||
| 167 | |||
| 168 | (define-derived-mode log-edit-mode text-mode "Log-Edit" | ||
| 169 | "Major mode for entering commit messages. | ||
| 170 | This mode is intended for entering messages in a *cvs-commit* | ||
| 171 | buffer when using PCL-CVS. It provides a binding for the | ||
| 172 | \\[log-edit-done] command that should be used when done editing | ||
| 173 | to trigger the actual commit, as well as a few handy support | ||
| 174 | commands. | ||
| 175 | \\{log-edit-mode-map}") | ||
| 176 | |||
| 177 | (defun log-edit-hide-buf (&optional buf where) | ||
| 178 | (when (setq buf (get-buffer (or buf log-edit-files-buf))) | ||
| 179 | (let ((win (get-buffer-window buf where))) | ||
| 180 | (if win (ignore-errors (delete-window win)))) | ||
| 181 | (bury-buffer buf))) | ||
| 182 | |||
| 183 | (defun log-edit-done () | ||
| 184 | "Finish editing the log message and commit the files. | ||
| 185 | This can only be used in the *cvs-commit* buffer. | ||
| 186 | With a prefix argument, prompt for cvs commit flags. | ||
| 187 | If you want to abort the commit, simply delete the buffer." | ||
| 188 | (interactive) | ||
| 189 | (if (and (> (point-max) 1) | ||
| 190 | (/= (char-after (1- (point-max))) ?\n) | ||
| 191 | (or (eq log-edit-require-final-newline t) | ||
| 192 | (and log-edit-require-final-newline | ||
| 193 | (y-or-n-p | ||
| 194 | (format "Buffer %s does not end in newline. Add one? " | ||
| 195 | (buffer-name)))))) | ||
| 196 | (save-excursion | ||
| 197 | (goto-char (point-max)) | ||
| 198 | (insert ?\n))) | ||
| 199 | (if (boundp 'vc-comment-ring) (ring-insert vc-comment-ring (buffer-string))) | ||
| 200 | (let ((win (get-buffer-window log-edit-files-buf))) | ||
| 201 | (if (and log-edit-confirm | ||
| 202 | (not (and (eq log-edit-confirm 'changed) | ||
| 203 | (equal (log-edit-files) log-edit-initial-files))) | ||
| 204 | (progn | ||
| 205 | (log-edit-show-files) | ||
| 206 | (not (y-or-n-p "Really commit ? ")))) | ||
| 207 | (progn (when (not win) (log-edit-hide-buf)) | ||
| 208 | (message "Oh, well! Later maybe?")) | ||
| 209 | (run-hooks 'log-edit-done-hook) | ||
| 210 | (log-edit-hide-buf) | ||
| 211 | (unless log-edit-keep-buffer | ||
| 212 | (cvs-bury-buffer (current-buffer) | ||
| 213 | (when (boundp 'cvs-buffer) cvs-buffer))) | ||
| 214 | (call-interactively log-edit-callback)))) | ||
| 215 | |||
| 216 | (defun log-edit-files () | ||
| 217 | "Return the list of files that are about to be committed." | ||
| 218 | (ignore-errors (funcall log-edit-listfun))) | ||
| 219 | |||
| 220 | |||
| 221 | (defun log-edit-insert-changelog () | ||
| 222 | "Insert a log message by looking at the ChangeLog. | ||
| 223 | The idea is to write your ChangeLog entries first, and then use this | ||
| 224 | command to commit your changes. | ||
| 225 | |||
| 226 | To select default log text, we: | ||
| 227 | - find the ChangeLog entries for the files to be checked in, | ||
| 228 | - verify that the top entry in the ChangeLog is on the current date | ||
| 229 | and by the current user; if not, we don't provide any default text, | ||
| 230 | - search the ChangeLog entry for paragraphs containing the names of | ||
| 231 | the files we're checking in, and finally | ||
| 232 | - use those paragraphs as the log text." | ||
| 233 | (interactive) | ||
| 234 | (cvs-insert-changelog-entries (log-edit-files)) | ||
| 235 | (log-edit-delete-common-indentation)) | ||
| 236 | |||
| 237 | (defun log-edit-mode-help () | ||
| 238 | "Provide help for the `log-edit-mode-map'." | ||
| 239 | (interactive) | ||
| 240 | (if (eq last-command 'log-edit-mode-help) | ||
| 241 | (describe-function major-mode) | ||
| 242 | (message | ||
| 243 | (substitute-command-keys | ||
| 244 | "Type `\\[log-edit-done]' to finish commit. Try `\\[describe-function] log-edit-done' for more help.")))) | ||
| 245 | |||
| 246 | (defun log-edit-delete-common-indentation () | ||
| 247 | "Unindent the current buffer rigidly until at least one line is flush left." | ||
| 248 | (save-excursion | ||
| 249 | (let ((common (point-max))) | ||
| 250 | (goto-char (point-min)) | ||
| 251 | (while (< (point) (point-max)) | ||
| 252 | (if (not (looking-at "^[ \t]*$")) | ||
| 253 | (setq common (min common (current-indentation)))) | ||
| 254 | (forward-line 1)) | ||
| 255 | (indent-rigidly (point-min) (point-max) (- common))))) | ||
| 256 | |||
| 257 | (defun log-edit-show-files () | ||
| 258 | "Show the list of files to be committed." | ||
| 259 | (interactive) | ||
| 260 | (let* ((files (log-edit-files)) | ||
| 261 | (editbuf (current-buffer)) | ||
| 262 | (buf (get-buffer-create "*log-edit-files*"))) | ||
| 263 | (with-current-buffer buf | ||
| 264 | (log-edit-hide-buf buf 'all) | ||
| 265 | (setq buffer-read-only nil) | ||
| 266 | (erase-buffer) | ||
| 267 | (insert (mapconcat 'identity files "\n")) | ||
| 268 | (setq buffer-read-only t) | ||
| 269 | (goto-char (point-min)) | ||
| 270 | (save-selected-window | ||
| 271 | (cvs-pop-to-buffer-same-frame buf) | ||
| 272 | (shrink-window-if-larger-than-buffer) | ||
| 273 | (selected-window))))) | ||
| 274 | |||
| 275 | (defun log-edit-insert-cvs-template () | ||
| 276 | "Insert the template specified by the CVS administrator, if any." | ||
| 277 | (interactive) | ||
| 278 | (when (file-readable-p "CVS/Template") | ||
| 279 | (insert-file-contents "CVS/Template"))) | ||
| 280 | |||
| 281 | |||
| 282 | (defun log-edit-add-to-changelog () | ||
| 283 | "Insert this log message into the appropriate ChangeLog file." | ||
| 284 | (interactive) | ||
| 285 | ;; Yuck! | ||
| 286 | (unless (string= (buffer-string) (ring-ref vc-comment-ring 0)) | ||
| 287 | (ring-insert vc-comment-ring (buffer-string))) | ||
| 288 | (dolist (f (log-edit-files)) | ||
| 289 | (let ((buffer-file-name (expand-file-name f))) | ||
| 290 | (save-excursion | ||
| 291 | (vc-comment-to-change-log))))) | ||
| 292 | |||
| 293 | ;;;; | ||
| 294 | ;;;; functions for getting commit message from ChangeLog a file... | ||
| 295 | ;;;; Courtesy Jim Blandy | ||
| 296 | ;;;; | ||
| 297 | |||
| 298 | (defun cvs-narrow-changelog () | ||
| 299 | "Narrow to the top page of the current buffer, a ChangeLog file. | ||
| 300 | Actually, the narrowed region doesn't include the date line. | ||
| 301 | A \"page\" in a ChangeLog file is the area between two dates." | ||
| 302 | (or (eq major-mode 'change-log-mode) | ||
| 303 | (error "cvs-narrow-changelog: current buffer isn't a ChangeLog")) | ||
| 304 | |||
| 305 | (goto-char (point-min)) | ||
| 306 | |||
| 307 | ;; Skip date line and subsequent blank lines. | ||
| 308 | (forward-line 1) | ||
| 309 | (if (looking-at "[ \t\n]*\n") | ||
| 310 | (goto-char (match-end 0))) | ||
| 311 | |||
| 312 | (let ((start (point))) | ||
| 313 | (forward-page 1) | ||
| 314 | (narrow-to-region start (point)) | ||
| 315 | (goto-char (point-min)))) | ||
| 316 | |||
| 317 | (defun cvs-changelog-paragraph () | ||
| 318 | "Return the bounds of the ChangeLog paragraph containing point. | ||
| 319 | If we are between paragraphs, return the previous paragraph." | ||
| 320 | (save-excursion | ||
| 321 | (beginning-of-line) | ||
| 322 | (if (looking-at "^[ \t]*$") | ||
| 323 | (skip-chars-backward " \t\n" (point-min))) | ||
| 324 | (list (progn | ||
| 325 | (if (re-search-backward "^[ \t]*\n" nil 'or-to-limit) | ||
| 326 | (goto-char (match-end 0))) | ||
| 327 | (point)) | ||
| 328 | (if (re-search-forward "^[ \t\n]*$" nil t) | ||
| 329 | (match-beginning 0) | ||
| 330 | (point))))) | ||
| 331 | |||
| 332 | (defun cvs-changelog-subparagraph () | ||
| 333 | "Return the bounds of the ChangeLog subparagraph containing point. | ||
| 334 | A subparagraph is a block of non-blank lines beginning with an asterisk. | ||
| 335 | If we are between sub-paragraphs, return the previous subparagraph." | ||
| 336 | (save-excursion | ||
| 337 | (end-of-line) | ||
| 338 | (if (search-backward "*" nil t) | ||
| 339 | (list (progn (beginning-of-line) (point)) | ||
| 340 | (progn | ||
| 341 | (forward-line 1) | ||
| 342 | (if (re-search-forward "^[ \t]*[\n*]" nil t) | ||
| 343 | (match-beginning 0) | ||
| 344 | (point-max)))) | ||
| 345 | (list (point) (point))))) | ||
| 346 | |||
| 347 | (defun cvs-changelog-entry () | ||
| 348 | "Return the bounds of the ChangeLog entry containing point. | ||
| 349 | The variable `cvs-changelog-full-paragraphs' decides whether an | ||
| 350 | \"entry\" is a paragraph or a subparagraph; see its documentation string | ||
| 351 | for more details." | ||
| 352 | (if cvs-changelog-full-paragraphs | ||
| 353 | (cvs-changelog-paragraph) | ||
| 354 | (cvs-changelog-subparagraph))) | ||
| 355 | |||
| 356 | (defvar user-full-name) | ||
| 357 | (defvar user-mail-address) | ||
| 358 | (defun cvs-changelog-ours-p () | ||
| 359 | "See if ChangeLog entry at point is for the current user, today. | ||
| 360 | Return non-nil iff it is." | ||
| 361 | ;; Code adapted from add-change-log-entry. | ||
| 362 | (let ((name (or (and (boundp 'add-log-full-name) add-log-full-name) | ||
| 363 | (and (fboundp 'user-full-name) (user-full-name)) | ||
| 364 | (and (boundp 'user-full-name) user-full-name))) | ||
| 365 | (mail (or (and (boundp 'add-log-mailing-address) add-log-mailing-address) | ||
| 366 | ;;(and (fboundp 'user-mail-address) (user-mail-address)) | ||
| 367 | (and (boundp 'user-mail-address) user-mail-address))) | ||
| 368 | (time (or (and (boundp 'add-log-time-format) | ||
| 369 | (functionp add-log-time-format) | ||
| 370 | (funcall add-log-time-format)) | ||
| 371 | (format-time-string "%Y-%m-%d")))) | ||
| 372 | (looking-at (regexp-quote (format "%s %s <%s>" time name mail))))) | ||
| 373 | |||
| 374 | (defun cvs-changelog-entries (file) | ||
| 375 | "Return the ChangeLog entries for FILE, and the ChangeLog they came from. | ||
| 376 | The return value looks like this: | ||
| 377 | (LOGBUFFER (ENTRYSTART . ENTRYEND) ...) | ||
| 378 | where LOGBUFFER is the name of the ChangeLog buffer, and each | ||
| 379 | \(ENTRYSTART . ENTRYEND\) pair is a buffer region." | ||
| 380 | (save-excursion | ||
| 381 | (let ((changelog-file-name | ||
| 382 | (let ((default-directory | ||
| 383 | (file-name-directory (expand-file-name file)))) | ||
| 384 | ;; `find-change-log' uses `change-log-default-name' if set | ||
| 385 | ;; and sets it before exiting, so we need to work around | ||
| 386 | ;; that memoizing which is undesired here | ||
| 387 | (setq change-log-default-name nil) | ||
| 388 | (find-change-log)))) | ||
| 389 | (set-buffer (find-file-noselect changelog-file-name)) | ||
| 390 | (unless (eq major-mode 'change-log-mode) (change-log-mode)) | ||
| 391 | (goto-char (point-min)) | ||
| 392 | (if (looking-at "\\s-*\n") (goto-char (match-end 0))) | ||
| 393 | (if (not (cvs-changelog-ours-p)) | ||
| 394 | (list (current-buffer)) | ||
| 395 | (save-restriction | ||
| 396 | (cvs-narrow-changelog) | ||
| 397 | (goto-char (point-min)) | ||
| 398 | |||
| 399 | ;; Search for the name of FILE relative to the ChangeLog. If that | ||
| 400 | ;; doesn't occur anywhere, they're not using full relative | ||
| 401 | ;; filenames in the ChangeLog, so just look for FILE; we'll accept | ||
| 402 | ;; some false positives. | ||
| 403 | (let ((pattern (file-relative-name | ||
| 404 | file (file-name-directory changelog-file-name)))) | ||
| 405 | (if (or (string= pattern "") | ||
| 406 | (not (save-excursion | ||
| 407 | (search-forward pattern nil t)))) | ||
| 408 | (setq pattern (file-name-nondirectory file))) | ||
| 409 | |||
| 410 | (let (texts) | ||
| 411 | (while (search-forward pattern nil t) | ||
| 412 | (let ((entry (cvs-changelog-entry))) | ||
| 413 | (push entry texts) | ||
| 414 | (goto-char (elt entry 1)))) | ||
| 415 | |||
| 416 | (cons (current-buffer) texts)))))))) | ||
| 417 | |||
| 418 | (defun cvs-changelog-insert-entries (buffer regions) | ||
| 419 | "Insert those regions in BUFFER specified in REGIONS. | ||
| 420 | Sort REGIONS front-to-back first." | ||
| 421 | (let ((regions (sort regions 'car-less-than-car)) | ||
| 422 | (last)) | ||
| 423 | (dolist (region regions) | ||
| 424 | (when (and last (< last (car region))) (newline)) | ||
| 425 | (setq last (elt region 1)) | ||
| 426 | (apply 'insert-buffer-substring buffer region)))) | ||
| 427 | |||
| 428 | (defun cvs-insert-changelog-entries (files) | ||
| 429 | "Given a list of files FILES, insert the ChangeLog entries for them." | ||
| 430 | (let ((buffer-entries nil)) | ||
| 431 | |||
| 432 | ;; Add each buffer to buffer-entries, and associate it with the list | ||
| 433 | ;; of entries we want from that file. | ||
| 434 | (dolist (file files) | ||
| 435 | (let* ((entries (cvs-changelog-entries file)) | ||
| 436 | (pair (assq (car entries) buffer-entries))) | ||
| 437 | (if pair | ||
| 438 | (setcdr pair (cvs-union (cdr pair) (cdr entries))) | ||
| 439 | (push entries buffer-entries)))) | ||
| 440 | |||
| 441 | ;; Now map over each buffer in buffer-entries, sort the entries for | ||
| 442 | ;; each buffer, and extract them as strings. | ||
| 443 | (dolist (buffer-entry buffer-entries) | ||
| 444 | (cvs-changelog-insert-entries (car buffer-entry) (cdr buffer-entry)) | ||
| 445 | (when (cdr buffer-entry) (newline))))) | ||
| 446 | |||
| 447 | (provide 'log-edit) | ||
| 448 | ;;; log-edit.el ends here | ||
diff --git a/lisp/log-view.el b/lisp/log-view.el new file mode 100644 index 00000000000..c157b392ad9 --- /dev/null +++ b/lisp/log-view.el | |||
| @@ -0,0 +1,189 @@ | |||
| 1 | ;;; log-view.el --- Major mode for browsing CVS log output | ||
| 2 | |||
| 3 | ;; Copyright (C) 1999-2000 Free Software Foundation, Inc. | ||
| 4 | |||
| 5 | ;; Author: Stefan Monnier <monnier@cs.yale.edu> | ||
| 6 | ;; Keywords: pcl-cvs cvs log | ||
| 7 | ;; Version: $Name: $ | ||
| 8 | ;; Revision: $Id: log-view.el,v 1.2 2000/03/03 20:58:09 monnier Exp $ | ||
| 9 | |||
| 10 | ;; This file is part of GNU Emacs. | ||
| 11 | |||
| 12 | ;; GNU Emacs is free software; you can redistribute it and/or modify | ||
| 13 | ;; it under the terms of the GNU General Public License as published by | ||
| 14 | ;; the Free Software Foundation; either version 2, or (at your option) | ||
| 15 | ;; any later version. | ||
| 16 | |||
| 17 | ;; GNU Emacs is distributed in the hope that it will be useful, | ||
| 18 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 19 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 20 | ;; GNU General Public License for more details. | ||
| 21 | |||
| 22 | ;; You should have received a copy of the GNU General Public License | ||
| 23 | ;; along with GNU Emacs; see the file COPYING. If not, write to the | ||
| 24 | ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, | ||
| 25 | ;; Boston, MA 02111-1307, USA. | ||
| 26 | |||
| 27 | ;;; Commentary: | ||
| 28 | |||
| 29 | ;; Todo: | ||
| 30 | |||
| 31 | ;; - extract version info in log-view-current-tag | ||
| 32 | ;; - add support for SCCS' output format | ||
| 33 | ;; - add compatibility with cvs-log.el | ||
| 34 | ;; - add ability to modify a log-entry (via cvs-mode-admin ;-) | ||
| 35 | |||
| 36 | ;;; Code: | ||
| 37 | |||
| 38 | (eval-when-compile (require 'cl)) | ||
| 39 | ;;(require 'pcvs-defs) | ||
| 40 | (require 'pcvs-util) | ||
| 41 | |||
| 42 | |||
| 43 | (defgroup log-view nil | ||
| 44 | "Major mode for browsing log output for PCL-CVS." | ||
| 45 | :group 'pcl-cvs | ||
| 46 | :prefix "log-view-") | ||
| 47 | |||
| 48 | (easy-mmode-defmap log-view-mode-map | ||
| 49 | '(("n" . log-view-next-message) | ||
| 50 | ("N" . log-view-next-file) | ||
| 51 | ("M-n" . log-view-next-file) | ||
| 52 | ("p" . log-view-prev-message) | ||
| 53 | ("P" . log-view-prev-file) | ||
| 54 | ("M-p" . log-view-prev-file)) | ||
| 55 | "Log-View's keymap." | ||
| 56 | :group 'log-view | ||
| 57 | :inherit 'cvs-mode-map) | ||
| 58 | |||
| 59 | (defvar log-view-mode-hook nil | ||
| 60 | "Hook run at the end of `log-view-mode'.") | ||
| 61 | |||
| 62 | (defface log-view-file-face | ||
| 63 | '((((class color) (background light)) | ||
| 64 | (:background "grey70" :bold t)) | ||
| 65 | (t (:bold t))) | ||
| 66 | "Face for the file header line in `log-view-mode'." | ||
| 67 | :group 'log-view) | ||
| 68 | (defvar log-view-file-face 'log-view-file-face) | ||
| 69 | |||
| 70 | (defface log-view-message-face | ||
| 71 | '((((class color) (background light)) | ||
| 72 | (:background "grey85")) | ||
| 73 | (t (:bold t))) | ||
| 74 | "Face for the message header line in `log-view-mode'." | ||
| 75 | :group 'log-view) | ||
| 76 | (defvar log-view-message-face 'log-view-message-face) | ||
| 77 | |||
| 78 | (defconst log-view-file-re | ||
| 79 | (concat "^\\(" | ||
| 80 | "Working file: \\(.+\\)" | ||
| 81 | "\\|SCCS/s\\.\\(.+\\):" | ||
| 82 | "\\)\n")) | ||
| 83 | (defconst log-view-message-re "^----------------------------$") | ||
| 84 | |||
| 85 | (defconst log-view-font-lock-keywords | ||
| 86 | `((,log-view-file-re | ||
| 87 | (2 'cvs-filename-face nil t) | ||
| 88 | (3 'cvs-filename-face nil t) | ||
| 89 | (0 'log-view-file-face append)) | ||
| 90 | (,log-view-message-re . log-view-message-face))) | ||
| 91 | (defconst log-view-font-lock-defaults | ||
| 92 | '(log-view-font-lock-keywords t nil nil nil)) | ||
| 93 | |||
| 94 | ;;;; | ||
| 95 | ;;;; Actual code | ||
| 96 | ;;;; | ||
| 97 | |||
| 98 | ;;;###autoload | ||
| 99 | (autoload 'log-view-mode "log-view" "Major mode for browsing CVS log output." t) | ||
| 100 | (eval-when-compile (autoload 'easy-mmode-define-derived-mode "easy-mmode")) | ||
| 101 | (easy-mmode-define-derived-mode log-view-mode fundamental-mode "Log-View" | ||
| 102 | "Major mode for browsing CVS log output." | ||
| 103 | (set (make-local-variable 'font-lock-defaults) log-view-font-lock-defaults) | ||
| 104 | (set (make-local-variable 'cvs-minor-wrap-function) 'log-view-minor-wrap)) | ||
| 105 | |||
| 106 | ;;;; | ||
| 107 | ;;;; Navigation | ||
| 108 | ;;;; | ||
| 109 | |||
| 110 | (defun log-view-next-message (&optional count) | ||
| 111 | "Move to next (COUNT'th) log message." | ||
| 112 | (interactive "p") | ||
| 113 | (unless count (setq count 1)) | ||
| 114 | (if (< count 0) (log-view-prev-message (- count)) | ||
| 115 | (when (looking-at log-view-message-re) (incf count)) | ||
| 116 | (re-search-forward log-view-message-re nil nil count) | ||
| 117 | (goto-char (match-beginning 0)))) | ||
| 118 | |||
| 119 | (defun log-view-next-file (&optional count) | ||
| 120 | "Move to next (COUNT'th) file." | ||
| 121 | (interactive "p") | ||
| 122 | (unless count (setq count 1)) | ||
| 123 | (if (< count 0) (log-view-prev-file (- count)) | ||
| 124 | (when (looking-at log-view-file-re) (incf count)) | ||
| 125 | (re-search-forward log-view-file-re nil nil count) | ||
| 126 | (goto-char (match-beginning 0)))) | ||
| 127 | |||
| 128 | (defun log-view-prev-message (&optional count) | ||
| 129 | "Move to previous (COUNT'th) log message." | ||
| 130 | (interactive "p") | ||
| 131 | (unless count (setq count 1)) | ||
| 132 | (if (< count 0) (log-view-next-message (- count)) | ||
| 133 | (re-search-backward log-view-message-re nil nil count))) | ||
| 134 | |||
| 135 | (defun log-view-prev-file (&optional count) | ||
| 136 | "Move to previous (COUNT'th) file." | ||
| 137 | (interactive "p") | ||
| 138 | (unless count (setq count 1)) | ||
| 139 | (if (< count 0) (log-view-next-file (- count)) | ||
| 140 | (re-search-backward log-view-file-re nil nil count))) | ||
| 141 | |||
| 142 | ;;;; | ||
| 143 | ;;;; Linkage to PCL-CVS (mostly copied from cvs-status.el) | ||
| 144 | ;;;; | ||
| 145 | |||
| 146 | (defconst log-view-dir-re "^cvs[.ex]* [a-z]+: Logging \\(.+\\)$") | ||
| 147 | |||
| 148 | (defun log-view-current-file () | ||
| 149 | (save-excursion | ||
| 150 | (forward-line 1) | ||
| 151 | (or (re-search-backward log-view-file-re nil t) | ||
| 152 | (re-search-forward log-view-file-re)) | ||
| 153 | (let* ((file (or (match-string 2) (match-string 3))) | ||
| 154 | (cvsdir (and (re-search-backward log-view-dir-re nil t) | ||
| 155 | (match-string 1))) | ||
| 156 | (pcldir (and (re-search-backward cvs-pcl-cvs-dirchange-re nil t) | ||
| 157 | (match-string 1))) | ||
| 158 | (dir "")) | ||
| 159 | (let ((default-directory "")) | ||
| 160 | (when pcldir (setq dir (expand-file-name pcldir dir))) | ||
| 161 | (when cvsdir (setq dir (expand-file-name cvsdir dir))) | ||
| 162 | (expand-file-name file dir))))) | ||
| 163 | |||
| 164 | (defun log-view-current-tag () | ||
| 165 | nil);; FIXME | ||
| 166 | |||
| 167 | (defun log-view-minor-wrap (buf f) | ||
| 168 | (let ((data (with-current-buffer buf | ||
| 169 | (cons | ||
| 170 | (cons (log-view-current-file) | ||
| 171 | (log-view-current-tag)) | ||
| 172 | (when (ignore-errors (mark)) | ||
| 173 | ;; `mark-active' is not provided by XEmacs :-( | ||
| 174 | (save-excursion | ||
| 175 | (goto-char (mark)) | ||
| 176 | (cons (log-view-current-file) | ||
| 177 | (log-view-current-tag)))))))) | ||
| 178 | (let ((cvs-branch-prefix (cdar data)) | ||
| 179 | (cvs-secondary-branch-prefix (and (cdar data) (cddr data))) | ||
| 180 | (cvs-minor-current-files | ||
| 181 | (cons (caar data) | ||
| 182 | (when (and (cadr data) (not (equal (caar data) (cadr data)))) | ||
| 183 | (list (cadr data))))) | ||
| 184 | ;; FIXME: I need to force because the fileinfos are UNKNOWN | ||
| 185 | (cvs-force-command "/F")) | ||
| 186 | (funcall f)))) | ||
| 187 | |||
| 188 | (provide 'log-view) | ||
| 189 | ;;; log-view.el ends here | ||
diff --git a/lisp/pcvs-defs.el b/lisp/pcvs-defs.el new file mode 100644 index 00000000000..1a7ea9a5173 --- /dev/null +++ b/lisp/pcvs-defs.el | |||
| @@ -0,0 +1,501 @@ | |||
| 1 | ;;; pcvs-defs.el --- variable definitions for PCL-CVS | ||
| 2 | |||
| 3 | ;; Copyright (C) 1991-2000 Free Software Foundation, Inc. | ||
| 4 | |||
| 5 | ;; Author: Stefan Monnier <monnier@cs.yale.edu> | ||
| 6 | ;; Keywords: pcl-cvs | ||
| 7 | ;; Version: $Name: $ | ||
| 8 | ;; Revision: $Id: pcl-cvs-defs.el,v 1.27 2000/03/03 20:58:09 monnier Exp $ | ||
| 9 | |||
| 10 | ;; This file is part of GNU Emacs. | ||
| 11 | |||
| 12 | ;; GNU Emacs is free software; you can redistribute it and/or modify | ||
| 13 | ;; it under the terms of the GNU General Public License as published by | ||
| 14 | ;; the Free Software Foundation; either version 2, or (at your option) | ||
| 15 | ;; any later version. | ||
| 16 | |||
| 17 | ;; GNU Emacs is distributed in the hope that it will be useful, | ||
| 18 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 19 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 20 | ;; GNU General Public License for more details. | ||
| 21 | |||
| 22 | ;; You should have received a copy of the GNU General Public License | ||
| 23 | ;; along with GNU Emacs; see the file COPYING. If not, write to the | ||
| 24 | ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, | ||
| 25 | ;; Boston, MA 02111-1307, USA. | ||
| 26 | |||
| 27 | ;;; Commentary: | ||
| 28 | |||
| 29 | |||
| 30 | ;;; Code: | ||
| 31 | |||
| 32 | (defconst pcl-cvs-version "$Name: $") | ||
| 33 | |||
| 34 | (eval-when-compile (require 'cl)) | ||
| 35 | (require 'pcvs-util) | ||
| 36 | |||
| 37 | ;;;; ------------------------------------------------------- | ||
| 38 | ;;;; START OF THINGS TO CHECK WHEN INSTALLING | ||
| 39 | |||
| 40 | (defvar cvs-program "cvs" | ||
| 41 | "*Name or full path of the cvs executable.") | ||
| 42 | |||
| 43 | (defvar cvs-version | ||
| 44 | (ignore-errors | ||
| 45 | (with-temp-buffer | ||
| 46 | (call-process "cvs" nil t nil "-v") | ||
| 47 | (goto-char (point-min)) | ||
| 48 | (when (re-search-forward "(CVS) \\([0-9]+\\)\\.\\([0-9]+\\)" nil t) | ||
| 49 | (cons (string-to-number (match-string 1)) | ||
| 50 | (string-to-number (match-string 2)))))) | ||
| 51 | "*Version of `cvs' installed on your system. | ||
| 52 | It must be in the (MAJOR . MINOR) format.") | ||
| 53 | |||
| 54 | ;; FIXME: this is only used by cvs-mode-diff-backup | ||
| 55 | (defvar cvs-diff-program (or (and (boundp 'diff-command) diff-command) "diff") | ||
| 56 | "*Name or full path of the best diff program you've got. | ||
| 57 | NOTE: there are some nasty bugs in the context diff variants of some vendor | ||
| 58 | versions, such as the one in SunOS-4.") | ||
| 59 | |||
| 60 | ;;;; END OF THINGS TO CHECK WHEN INSTALLING | ||
| 61 | ;;;; -------------------------------------------------------- | ||
| 62 | |||
| 63 | ;;;; | ||
| 64 | ;;;; User configuration variables: | ||
| 65 | ;;;; | ||
| 66 | ;;;; NOTE: these should be set in your ~/.emacs (or site-lisp/default.el) file. | ||
| 67 | ;;;; | ||
| 68 | |||
| 69 | (defgroup pcl-cvs nil | ||
| 70 | "Special support for the CVS versioning system." | ||
| 71 | :group 'tools | ||
| 72 | :prefix "cvs-") | ||
| 73 | |||
| 74 | ;; | ||
| 75 | ;; cvsrc options | ||
| 76 | ;; | ||
| 77 | |||
| 78 | (defcustom cvs-cvsrc-file "~/.cvsrc" | ||
| 79 | "Path to your cvsrc file." | ||
| 80 | :group 'pcl-cvs | ||
| 81 | :type '(file)) | ||
| 82 | |||
| 83 | (defvar cvs-shared-start 4 | ||
| 84 | "Index of the first shared flag. | ||
| 85 | If set to 4, for instance, a numeric argument smaller than 4 will | ||
| 86 | select a non-shared flag, while a numeric argument greater than 3 | ||
| 87 | will select a shared-flag.") | ||
| 88 | |||
| 89 | (defvar cvs-shared-flags (make-list cvs-shared-start nil) | ||
| 90 | "List of flags whose settings is shared among several commands.") | ||
| 91 | |||
| 92 | (defvar cvs-cvsroot nil | ||
| 93 | "*Specifies where the (current) cvs master repository is. | ||
| 94 | Overrides the environment variable $CVSROOT by sending \" -d dir\" to | ||
| 95 | all CVS commands. This switch is useful if you have multiple CVS | ||
| 96 | repositories. It can be set interactively with \\[cvs-change-cvsroot.] | ||
| 97 | There is no need to set this if $CVSROOT is set to a correct value.") | ||
| 98 | |||
| 99 | (defcustom cvs-auto-remove-handled nil | ||
| 100 | "*If up-to-date files should be acknowledged automatically. | ||
| 101 | If T, they will be removed from the *cvs* buffer after every command. | ||
| 102 | If DELAYED, they will be removed from the *cvs* buffer before every command. | ||
| 103 | If STATUS, they will only be removed after a `cvs-mode-status' command. | ||
| 104 | Else, they will never be automatically removed from the *cvs* buffer." | ||
| 105 | :group 'pcl-cvs | ||
| 106 | :type '(choice (const nil) (const status) (const delayed) (const t))) | ||
| 107 | |||
| 108 | (defcustom cvs-auto-remove-directories 'handled | ||
| 109 | "*If ALL, directory entries will never be shown. | ||
| 110 | If HANLDED, only non-handled directories will be shown. | ||
| 111 | If EMPTY, only non-empty directories will be shown." | ||
| 112 | :group 'pcl-cvs | ||
| 113 | :type '(choice (const :tag "No" nil) (const all) (const handled) (const empty))) | ||
| 114 | |||
| 115 | (defcustom cvs-auto-revert t | ||
| 116 | "*Non-nil if changed files should automatically be reverted." | ||
| 117 | :group 'pcl-cvs | ||
| 118 | :type '(boolean)) | ||
| 119 | |||
| 120 | (defcustom cvs-sort-ignore-file t | ||
| 121 | "*Non-nil if `cvs-mode-ignore' should sort the .cvsignore automatically." | ||
| 122 | :group 'pcl-cvs | ||
| 123 | :type '(boolean)) | ||
| 124 | |||
| 125 | (defcustom cvs-force-dir-tag t | ||
| 126 | "*If non-nil, tagging can only be applied to directories. | ||
| 127 | Tagging should generally be applied a directory at a time, but sometimes it is | ||
| 128 | useful to be able to tag a single file. The normal way to do that is to use | ||
| 129 | `cvs-mode-force-command' so as to temporarily override the restrictions," | ||
| 130 | :group 'pcl-cvs | ||
| 131 | :type '(boolean)) | ||
| 132 | |||
| 133 | (defcustom cvs-default-ignore-marks nil | ||
| 134 | "*Non-nil if cvs mode commands should ignore any marked files. | ||
| 135 | Normally they run on the files that are marked (with `cvs-mode-mark'), | ||
| 136 | or the file under the cursor if no files are marked. If this variable | ||
| 137 | is set to a non-nil value they will by default run on the file on the | ||
| 138 | current line. See also `cvs-ignore-marks'" | ||
| 139 | :group 'pcl-cvs | ||
| 140 | :type '(boolean)) | ||
| 141 | |||
| 142 | (defvar cvs-diff-ignore-marks t | ||
| 143 | "Obsolete variable: use cvs-ignore-marks instead.") | ||
| 144 | |||
| 145 | (defcustom cvs-invert-ignore-marks | ||
| 146 | (let ((l ())) | ||
| 147 | (unless (equal cvs-diff-ignore-marks cvs-default-ignore-marks) | ||
| 148 | (push "diff" l)) | ||
| 149 | (when (and cvs-force-dir-tag (not cvs-default-ignore-marks)) | ||
| 150 | (push "tag" l)) | ||
| 151 | l) | ||
| 152 | "*List of cvs commands that invert the default ignore-mark behavior. | ||
| 153 | Commands in this set will use the opposite default from the one set | ||
| 154 | in `cvs-default-ignore-marks'." | ||
| 155 | :group 'pcl-cvs | ||
| 156 | :type '(set (const "diff") | ||
| 157 | (const "tag") | ||
| 158 | (const "ignore"))) | ||
| 159 | |||
| 160 | (defcustom cvs-confirm-removals t | ||
| 161 | "*Ask for confirmation before removing files. | ||
| 162 | Non-nil means that PCL-CVS will ask confirmation before removing files | ||
| 163 | except for files whose content can readily be recovered from the repository. | ||
| 164 | A value of LIST means that the list of files to be deleted will be | ||
| 165 | displayed when asking for confirmation." | ||
| 166 | :group 'pcl-cvs | ||
| 167 | :type '(choice (const list) | ||
| 168 | (const t) | ||
| 169 | (const nil))) | ||
| 170 | |||
| 171 | (defcustom cvs-add-default-message nil | ||
| 172 | "*Default message to use when adding files. | ||
| 173 | If set to NIL, `cvs-mode-add' will always prompt for a message." | ||
| 174 | :group 'pcl-cvs | ||
| 175 | :type '(choice (const :tag "Prompt" nil) | ||
| 176 | (string))) | ||
| 177 | |||
| 178 | (defvar cvs-diff-buffer-name "*cvs-diff*" | ||
| 179 | "Obsolete variable: use `cvs-buffer-name-alist' instead.") | ||
| 180 | |||
| 181 | (defcustom cvs-find-file-and-jump t | ||
| 182 | "Jump to the modified area when finding a file. | ||
| 183 | If non-nil, `cvs-mode-file-file' will place the cursor at the beginning of | ||
| 184 | the modified area. If the file is not locally modified, this will obviously | ||
| 185 | have no effect." | ||
| 186 | :group 'pcl-cvs | ||
| 187 | :type '(boolean)) | ||
| 188 | |||
| 189 | (defcustom cvs-buffer-name-alist | ||
| 190 | '(("diff" cvs-diff-buffer-name diff-mode) | ||
| 191 | ("status" "*cvs-info*" cvs-status-mode) | ||
| 192 | ("tree" (format "*cvs-%s*" cmd) cvs-status-mode) | ||
| 193 | ("message" "*cvs-commit*" nil log-edit) | ||
| 194 | ("log" "*cvs-info*" log-view-mode)) | ||
| 195 | "*Buffer name and mode to be used for each command. | ||
| 196 | This is a list of elements of the form | ||
| 197 | |||
| 198 | (CMD BUFNAME MODE &optional POSTPROC) | ||
| 199 | |||
| 200 | CMD is the name of the command. | ||
| 201 | BUFNAME is an expression that should evaluate to a string used as | ||
| 202 | a buffer name. It can use the variable CMD if it wants to. | ||
| 203 | MODE is the command to use to setup the buffer. | ||
| 204 | POSTPROC is a function that should be executed when the command terminates | ||
| 205 | |||
| 206 | The CMD used for `cvs-mode-commit' is \"message\". For that special | ||
| 207 | case, POSTPROC is called just after MODE with special arguments." | ||
| 208 | :group 'pcl-cvs | ||
| 209 | :type '(repeat | ||
| 210 | (list (choice (const "diff") | ||
| 211 | (const "status") | ||
| 212 | (const "tree") | ||
| 213 | (const "message") | ||
| 214 | (const "log") | ||
| 215 | (string)) | ||
| 216 | (choice (const "*vc-diff*") | ||
| 217 | (const "*cvs-info*") | ||
| 218 | (const "*cvs-commit*") | ||
| 219 | (const (expand-file-name "*cvs-commit*")) | ||
| 220 | (const (format "*cvs-%s*" cmd)) | ||
| 221 | (const (expand-file-name (format "*cvs-%s*" cmd))) | ||
| 222 | (sexp :value "my-cvs-info-buffer") | ||
| 223 | (const nil)) | ||
| 224 | (choice (function-item diff-mode) | ||
| 225 | (function-item cvs-edit-mode) | ||
| 226 | (function-item cvs-status-mode) | ||
| 227 | function | ||
| 228 | (const nil)) | ||
| 229 | (set :inline t | ||
| 230 | (choice (function-item cvs-status-cvstrees) | ||
| 231 | (function-item cvs-status-trees) | ||
| 232 | function))))) | ||
| 233 | |||
| 234 | (defvar cvs-buffer-name '(expand-file-name "*cvs*" dir) ;; "*cvs*" | ||
| 235 | "Name of the cvs buffer. | ||
| 236 | This expression will be evaluated in an environment where DIR is set to | ||
| 237 | the directory name of the cvs buffer.") | ||
| 238 | |||
| 239 | (defvar cvs-temp-buffer-name '(expand-file-name " *cvs-tmp*" dir) | ||
| 240 | "*Name of the cvs temporary buffer. | ||
| 241 | Output from cvs is placed here for asynchronous commands.") | ||
| 242 | |||
| 243 | (defcustom cvs-idiff-imerge-handlers | ||
| 244 | (if (fboundp 'ediff) | ||
| 245 | '(cvs-ediff-diff . cvs-ediff-merge) | ||
| 246 | '(cvs-emerge-diff . cvs-emerge-merge)) | ||
| 247 | "*Pair of functions to be used for resp. diff'ing and merg'ing interactively." | ||
| 248 | :group 'pcl-cvs | ||
| 249 | :type '(choice (const :tag "Ediff" (cvs-ediff-diff . cvs-ediff-merge)) | ||
| 250 | (const :tag "Emerge" (cvs-emerge-diff . cvs-emerge-merge)))) | ||
| 251 | |||
| 252 | (defvar pcl-cvs-load-hook nil | ||
| 253 | "Run after loading pcl-cvs.") | ||
| 254 | |||
| 255 | (defvar cvs-mode-hook nil | ||
| 256 | "Run after `cvs-mode' was setup.") | ||
| 257 | |||
| 258 | |||
| 259 | ;;;; | ||
| 260 | ;;;; Internal variables, used in the process buffer. | ||
| 261 | ;;;; | ||
| 262 | |||
| 263 | (defvar cvs-postprocess nil | ||
| 264 | "(Buffer local) what to do once the process exits.") | ||
| 265 | |||
| 266 | ;;;; | ||
| 267 | ;;;; Internal variables for the *cvs* buffer. | ||
| 268 | ;;;; | ||
| 269 | |||
| 270 | (defcustom cvs-reuse-cvs-buffer 'subdir | ||
| 271 | "When to reuse an existing cvs buffer. | ||
| 272 | Alternatives are: | ||
| 273 | CURRENT: just reuse the current buffer if it is a cvs buffer | ||
| 274 | SAMEDIR: reuse any cvs buffer displaying the same directory | ||
| 275 | SUBDIR: or reuse any cvs buffer displaying any sub- or super- directory | ||
| 276 | ALWAYS: reuse any cvs buffer." | ||
| 277 | :group 'pcl-cvs | ||
| 278 | :type '(choice (const always) (const subdir) (const samedir) (const current))) | ||
| 279 | |||
| 280 | (defvar cvs-temp-buffer nil | ||
| 281 | "(Buffer local) The temporary buffer associated with this *cvs* buffer.") | ||
| 282 | |||
| 283 | (defvar cvs-lock-file nil | ||
| 284 | "Full path to a lock file that CVS is waiting for (or was waiting for). | ||
| 285 | This variable is buffer local and only used in the *cvs* buffer.") | ||
| 286 | |||
| 287 | (defvar cvs-lock-file-regexp "^#cvs\\.\\([trw]fl\\.[-.a-z0-9]+\\|lock\\)\\'" | ||
| 288 | "Regexp matching the possible names of locks in the CVS repository.") | ||
| 289 | |||
| 290 | (defconst cvs-cursor-column 22 | ||
| 291 | "Column to position cursor in in `cvs-mode'.") | ||
| 292 | |||
| 293 | ;;;; | ||
| 294 | ;;;; Global internal variables | ||
| 295 | ;;;; | ||
| 296 | |||
| 297 | (defconst cvs-startup-message | ||
| 298 | (concat "PCL-CVS release " pcl-cvs-version) | ||
| 299 | "*Startup message for CVS.") | ||
| 300 | |||
| 301 | (defconst cvs-vendor-branch "1.1.1" | ||
| 302 | "The default branch used by CVS for vendor code.") | ||
| 303 | |||
| 304 | (defvar cvs-menu | ||
| 305 | '("CVS" | ||
| 306 | ["Open File.." cvs-mode-find-file t] | ||
| 307 | [" ..Other Window" cvs-mode-find-file-other-window t] | ||
| 308 | ["Interactive Merge" cvs-mode-imerge t] | ||
| 309 | ["Interactive Diff" cvs-mode-idiff t] | ||
| 310 | ["View Diff" cvs-mode-diff (cvs-enabledp 'diff)] | ||
| 311 | ["Diff with Vendor" cvs-mode-diff-vendor t] | ||
| 312 | ["Diff with Backup" cvs-mode-diff-backup t] | ||
| 313 | ["View Log" cvs-mode-log t] | ||
| 314 | ["View Status" cvs-mode-status t] | ||
| 315 | "----" | ||
| 316 | ["Update" cvs-mode-update (cvs-enabledp 'update)] | ||
| 317 | ["Re-Examine" cvs-mode-examine t] | ||
| 318 | ["Commit" cvs-mode-commit-setup (cvs-enabledp 'commit)] | ||
| 319 | ["Undo Changes" cvs-mode-undo (cvs-enabledp 'undo)] | ||
| 320 | ["Add" cvs-mode-add (cvs-enabledp 'add)] | ||
| 321 | ["Remove" cvs-mode-remove (cvs-enabledp 'remove)] | ||
| 322 | ["Ignore" cvs-mode-ignore (cvs-enabledp 'ignore)] | ||
| 323 | ["Add ChangeLog" cvs-mode-add-change-log-entry-other-window t] | ||
| 324 | "----" | ||
| 325 | ["Mark All" cvs-mode-mark-all-files t] | ||
| 326 | ["Unmark All" cvs-mode-unmark-all-files t] | ||
| 327 | ["Hide Handled" cvs-mode-remove-handled t] | ||
| 328 | "----" | ||
| 329 | ;; ["Update Directory" cvs-update t] | ||
| 330 | ;; ["Examine Directory" cvs-examine t] | ||
| 331 | ;; ["Status Directory" cvs-status t] | ||
| 332 | ;; ["Checkout Module" cvs-checkout t] | ||
| 333 | ;; "----" | ||
| 334 | ["Quit" cvs-mode-quit t] | ||
| 335 | )) | ||
| 336 | |||
| 337 | (easy-mmode-defmap cvs-mode-diff-map | ||
| 338 | '(("=" . cvs-mode-diff) | ||
| 339 | ("b" . cvs-mode-diff-backup) | ||
| 340 | ("2" . cvs-mode-idiff-other) | ||
| 341 | ("h" . cvs-mode-diff-head) | ||
| 342 | ("v" . cvs-mode-diff-vendor) | ||
| 343 | ("?" . cvs-mode-diff-help) | ||
| 344 | ("e" . cvs-mode-idiff) | ||
| 345 | ("E" . cvs-mode-imerge)) | ||
| 346 | "Keymap for diff-related operations in `cvs-mode'.") | ||
| 347 | |||
| 348 | (easy-mmode-defmap cvs-mode-map | ||
| 349 | ;;(define-prefix-command 'cvs-mode-map-diff-prefix) | ||
| 350 | ;;(define-prefix-command 'cvs-mode-map-control-c-prefix) | ||
| 351 | `(;; simulate `suppress-keymap' | ||
| 352 | (self-insert-command . undefined) | ||
| 353 | (("0" "1" "2" "3" "4" "5" "6" "7" "8" "9") . digit-argument) | ||
| 354 | ("-" . negative-argument) | ||
| 355 | ;; various | ||
| 356 | (undo . cvs-mode-undo) | ||
| 357 | ("?" . cvs-help) | ||
| 358 | ("h" . cvs-help) | ||
| 359 | ("q" . cvs-bury-buffer) | ||
| 360 | ;;("Q" . kill-buffer) | ||
| 361 | ("F" . cvs-mode-set-flags) | ||
| 362 | ("\M-f" . cvs-mode-force-command) | ||
| 363 | ("\C-c\C-c" . cvs-mode-kill-process) | ||
| 364 | ;; marking | ||
| 365 | ("m" . cvs-mode-mark) | ||
| 366 | ("M" . cvs-mode-mark-all-files) | ||
| 367 | ("u" . cvs-mode-unmark) | ||
| 368 | ("\C-?". cvs-mode-unmark-up) | ||
| 369 | ("%" . cvs-mode-mark-matching-files) | ||
| 370 | ("T" . cvs-mode-toggle-marks) | ||
| 371 | ("\M-\C-?" . cvs-mode-unmark-all-files) | ||
| 372 | ;; navigation keys | ||
| 373 | (" " . cvs-mode-next-line) | ||
| 374 | ("n" . cvs-mode-next-line) | ||
| 375 | ("p" . cvs-mode-previous-line) | ||
| 376 | ;; M- keys are usually those that operate on modules | ||
| 377 | ;;("\M-C". cvs-mode-rcs2log) ; i.e. "Create a ChangeLog" | ||
| 378 | ;;("\M-t". cvs-rtag) | ||
| 379 | ;;("\M-l". cvs-rlog) | ||
| 380 | ("\M-c". cvs-checkout) | ||
| 381 | ("\M-e". cvs-examine) | ||
| 382 | ("g" . cvs-mode-revert-buffer) | ||
| 383 | ("\M-u". cvs-update) | ||
| 384 | ("\M-s". cvs-status) | ||
| 385 | ;; diff commands | ||
| 386 | ("=" . cvs-mode-diff) | ||
| 387 | ("d" . ,cvs-mode-diff-map) | ||
| 388 | ;; keys that operate on individual files | ||
| 389 | ("\C-k". cvs-mode-acknowledge) | ||
| 390 | ("A" . cvs-mode-add-change-log-entry-other-window) | ||
| 391 | ;;("B" . cvs-mode-byte-compile-files) | ||
| 392 | ("C" . cvs-mode-commit-setup) | ||
| 393 | ("O" . cvs-mode-update) | ||
| 394 | ("U" . cvs-mode-undo) | ||
| 395 | ("I" . cvs-mode-insert) | ||
| 396 | ("a" . cvs-mode-add) | ||
| 397 | ("b" . cvs-set-branch-prefix) | ||
| 398 | ("B" . cvs-set-secondary-branch-prefix) | ||
| 399 | ("c" . cvs-mode-commit) | ||
| 400 | ("e" . cvs-mode-examine) | ||
| 401 | ("f" . cvs-mode-find-file) | ||
| 402 | ("i" . cvs-mode-ignore) | ||
| 403 | ("l" . cvs-mode-log) | ||
| 404 | ("o" . cvs-mode-find-file-other-window) | ||
| 405 | ("r" . cvs-mode-remove) | ||
| 406 | ("s" . cvs-mode-status) | ||
| 407 | ("t" . cvs-mode-tag) | ||
| 408 | ;;("v" . cvs-mode-diff-vendor) | ||
| 409 | ("x" . cvs-mode-remove-handled) | ||
| 410 | ;; cvstree bindings | ||
| 411 | ("+" . cvs-mode-tree) | ||
| 412 | ;; mouse bindings | ||
| 413 | ([(down-mouse-3)] . cvs-menu) | ||
| 414 | ;; Emacs-21 toolbar | ||
| 415 | ;;([tool-bar item1] . (menu-item "Examine" cvs-examine :image (image :file "/usr/share/icons/xpaint.xpm" :type xpm))) | ||
| 416 | ;;([tool-bar item2] . (menu-item "Update" cvs-update :image (image :file "/usr/share/icons/mail1.xpm" :type xpm))) | ||
| 417 | ) | ||
| 418 | "Keymap for `cvs-mode'." | ||
| 419 | :dense t) | ||
| 420 | |||
| 421 | (fset 'cvs-mode-map cvs-mode-map) | ||
| 422 | |||
| 423 | ;; add the cvs-menu to the map so it's added whenever we are in cvs-mode | ||
| 424 | (when (ignore-errors (require 'easymenu)) | ||
| 425 | (easy-menu-define cvs-menu-map | ||
| 426 | cvs-mode-map | ||
| 427 | "Menu used in cvs-mode." | ||
| 428 | cvs-menu)) | ||
| 429 | |||
| 430 | ;;;; | ||
| 431 | ;;;; CVS-Minor mode | ||
| 432 | ;;;; | ||
| 433 | |||
| 434 | (defcustom cvs-minor-mode-prefix "\C-xc" | ||
| 435 | "Prefix key for the `cvs-mode' bindings in `cvs-minor-mode'." | ||
| 436 | :group 'pcl-cvs) | ||
| 437 | |||
| 438 | (easy-mmode-defmap cvs-minor-mode-map | ||
| 439 | `((,cvs-minor-mode-prefix . cvs-mode-map)) | ||
| 440 | "Keymap for `cvs-minor-mode', used in buffers related to pcl-cvs.") | ||
| 441 | |||
| 442 | (defvar cvs-buffer nil | ||
| 443 | "(Buffer local) The *cvs* buffer associated with this buffer.") | ||
| 444 | (put 'cvs-buffer 'permanent-local t) | ||
| 445 | ;;(make-variable-buffer-local 'cvs-buffer) | ||
| 446 | |||
| 447 | (defvar cvs-minor-wrap-function nil | ||
| 448 | "Function to call when switching to the *cvs* buffer. | ||
| 449 | Takes two arguments: | ||
| 450 | - a *cvs* buffer. | ||
| 451 | - a zero-arg function which is guaranteed not to switch buffer. | ||
| 452 | It is expected to call the function.") | ||
| 453 | ;;(make-variable-buffer-local 'cvs-minor-wrap-function) | ||
| 454 | |||
| 455 | (defvar cvs-minor-current-files) | ||
| 456 | ;;"Current files in a `cvs-minor-mode' buffer." | ||
| 457 | ;; This should stay `void' because we want to be able to tell the difference | ||
| 458 | ;; between an empty list and no list at all. | ||
| 459 | |||
| 460 | (defconst cvs-pcl-cvs-dirchange-re "^pcl-cvs: descending directory \\(.*\\)$") | ||
| 461 | |||
| 462 | ;;;; | ||
| 463 | ;;;; | ||
| 464 | ;;;; | ||
| 465 | |||
| 466 | ;;;###autoload | ||
| 467 | (if (progn (condition-case () (require 'easymenu) (error nil)) | ||
| 468 | (fboundp 'easy-menu-add-item)) | ||
| 469 | (easy-menu-add-item nil '("tools") | ||
| 470 | '("PCL CVS" | ||
| 471 | ["Update Directory" cvs-update t] | ||
| 472 | ["Examine Directory" cvs-examine t] | ||
| 473 | ["Status Directory" cvs-status t] | ||
| 474 | ["Checkout Module" cvs-checkout t]) "vc")) | ||
| 475 | |||
| 476 | |||
| 477 | ;; cvs-1.10 and above can take file arguments in other directories | ||
| 478 | ;; while others need to be executed once per directory | ||
| 479 | (defvar cvs-execute-single-dir | ||
| 480 | (if (and (consp cvs-version) | ||
| 481 | (or (>= (cdr cvs-version) 10) (> (car cvs-version) 1))) | ||
| 482 | '("status") | ||
| 483 | t) | ||
| 484 | "Whether cvs commands should be executed a directory at a time. | ||
| 485 | If a list, specifies for which commands the single-dir mode should be used. | ||
| 486 | If T, single-dir mode should be used for all operations. | ||
| 487 | |||
| 488 | CVS versions before 1.10 did not allow passing them arguments in different | ||
| 489 | directories, so pcl-cvs checks what version you're using to determine | ||
| 490 | whether to use the new feature or not. | ||
| 491 | Sadly, even with a new cvs executable, if you connect to an older cvs server | ||
| 492 | \(typically a cvs-1.9 on the server), the old restriction applies. In such | ||
| 493 | a case the sanity check made by pcl-cvs fails and you will have to manually | ||
| 494 | set this variable to T (until the cvs server is upgraded). | ||
| 495 | When the above problem occurs, pcl-cvs should (hopefully) catch cvs' error | ||
| 496 | message and replace it with a message tell you to change this variable.") | ||
| 497 | |||
| 498 | ;; | ||
| 499 | (provide 'pcvs-defs) | ||
| 500 | |||
| 501 | ;;; pcl-cvs-defs.el ends here | ||
diff --git a/lisp/pcvs-info.el b/lisp/pcvs-info.el new file mode 100644 index 00000000000..51b791e8ae3 --- /dev/null +++ b/lisp/pcvs-info.el | |||
| @@ -0,0 +1,455 @@ | |||
| 1 | ;;; pcvs-info.el --- Internal representation of a fileinfo entry | ||
| 2 | |||
| 3 | ;; Copyright (C) 1991-2000 Free Software Foundation, Inc. | ||
| 4 | |||
| 5 | ;; Author: Stefan Monnier <monnier@cs.yale.edu> | ||
| 6 | ;; Keywords: pcl-cvs | ||
| 7 | ;; Version: $Name: $ | ||
| 8 | ;; Revision: $Id: pcl-cvs-info.el,v 1.28 2000/03/05 21:32:21 monnier Exp $ | ||
| 9 | |||
| 10 | ;; This file is part of GNU Emacs. | ||
| 11 | |||
| 12 | ;; GNU Emacs is free software; you can redistribute it and/or modify | ||
| 13 | ;; it under the terms of the GNU General Public License as published by | ||
| 14 | ;; the Free Software Foundation; either version 2, or (at your option) | ||
| 15 | ;; any later version. | ||
| 16 | |||
| 17 | ;; GNU Emacs is distributed in the hope that it will be useful, | ||
| 18 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 19 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 20 | ;; GNU General Public License for more details. | ||
| 21 | |||
| 22 | ;; You should have received a copy of the GNU General Public License | ||
| 23 | ;; along with GNU Emacs; see the file COPYING. If not, write to the | ||
| 24 | ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, | ||
| 25 | ;; Boston, MA 02111-1307, USA. | ||
| 26 | |||
| 27 | ;;; Commentary: | ||
| 28 | |||
| 29 | ;; The cvs-fileinfo data structure: | ||
| 30 | ;; | ||
| 31 | ;; When the `cvs update' is ready we parse the output. Every file | ||
| 32 | ;; that is affected in some way is added to the cookie collection as | ||
| 33 | ;; a "fileinfo" (as defined below in cvs-create-fileinfo). | ||
| 34 | |||
| 35 | ;;; Code: | ||
| 36 | |||
| 37 | (eval-when-compile (require 'cl)) | ||
| 38 | (require 'pcvs-util) | ||
| 39 | ;;(require 'pcvs-defs) | ||
| 40 | |||
| 41 | ;;;; | ||
| 42 | ;;;; config variables | ||
| 43 | ;;;; | ||
| 44 | |||
| 45 | (defcustom cvs-display-full-path t | ||
| 46 | "*Specifies how the filenames should look like in the listing. | ||
| 47 | If t, their full path name will be displayed, else only the filename." | ||
| 48 | :group 'pcl-cvs | ||
| 49 | :type '(boolean)) | ||
| 50 | |||
| 51 | (defvar global-font-lock-mode) | ||
| 52 | (defvar font-lock-auto-fontify) | ||
| 53 | (defcustom cvs-highlight | ||
| 54 | (or (and (boundp 'font-lock-auto-fontify) font-lock-auto-fontify) | ||
| 55 | (and (boundp 'global-font-lock-mode) global-font-lock-mode)) | ||
| 56 | "*Whether to use text highlighting (à la font-lock) or not." | ||
| 57 | :group 'pcl-cvs | ||
| 58 | :type '(boolean)) | ||
| 59 | |||
| 60 | (defcustom cvs-allow-dir-commit nil | ||
| 61 | "*Allow `cvs-mode-commit' on directories. | ||
| 62 | If you commit without any marked file and with the cursor positioned | ||
| 63 | on a directory entry, cvs would commit the whole directory. This seems | ||
| 64 | to confuse some users sometimes." | ||
| 65 | :group 'pcl-cvs | ||
| 66 | :type '(boolean)) | ||
| 67 | |||
| 68 | |||
| 69 | ;;;; | ||
| 70 | ;;;; Faces for fontification | ||
| 71 | ;;;; | ||
| 72 | |||
| 73 | (defface cvs-header-face | ||
| 74 | '((((class color) (background dark)) | ||
| 75 | (:foreground "lightyellow" :bold t)) | ||
| 76 | (((class color) (background light)) | ||
| 77 | (:foreground "blue4" :bold t)) | ||
| 78 | (t (:bold t))) | ||
| 79 | "PCL-CVS face used to highlight directory changes." | ||
| 80 | :group 'pcl-cvs) | ||
| 81 | |||
| 82 | (defface cvs-filename-face | ||
| 83 | '((((class color) (background dark)) | ||
| 84 | (:foreground "lightblue")) | ||
| 85 | (((class color) (background light)) | ||
| 86 | (:foreground "blue4")) | ||
| 87 | (t ())) | ||
| 88 | "PCL-CVS face used to highlight file names." | ||
| 89 | :group 'pcl-cvs) | ||
| 90 | |||
| 91 | (defface cvs-unknown-face | ||
| 92 | '((((class color) (background dark)) | ||
| 93 | (:foreground "red")) | ||
| 94 | (((class color) (background light)) | ||
| 95 | (:foreground "red")) | ||
| 96 | (t (:italic t))) | ||
| 97 | "PCL-CVS face used to highlight unknown file status." | ||
| 98 | :group 'pcl-cvs) | ||
| 99 | |||
| 100 | (defface cvs-handled-face | ||
| 101 | '((((class color) (background dark)) | ||
| 102 | (:foreground "pink")) | ||
| 103 | (((class color) (background light)) | ||
| 104 | (:foreground "pink")) | ||
| 105 | (t ())) | ||
| 106 | "PCL-CVS face used to highlight handled file status." | ||
| 107 | :group 'pcl-cvs) | ||
| 108 | |||
| 109 | (defface cvs-need-action-face | ||
| 110 | '((((class color) (background dark)) | ||
| 111 | (:foreground "orange")) | ||
| 112 | (((class color) (background light)) | ||
| 113 | (:foreground "orange")) | ||
| 114 | (t (:italic t))) | ||
| 115 | "PCL-CVS face used to highlight status of files needing action." | ||
| 116 | :group 'pcl-cvs) | ||
| 117 | |||
| 118 | (defface cvs-marked-face | ||
| 119 | '((((class color) (background dark)) | ||
| 120 | (:foreground "green" :bold t)) | ||
| 121 | (((class color) (background light)) | ||
| 122 | (:foreground "green3" :bold t)) | ||
| 123 | (t (:bold t))) | ||
| 124 | "PCL-CVS face used to highlight marked file indicator." | ||
| 125 | :group 'pcl-cvs) | ||
| 126 | |||
| 127 | (defface cvs-msg-face | ||
| 128 | '((t (:italic t))) | ||
| 129 | "PCL-CVS face used to highlight CVS messages." | ||
| 130 | :group 'pcl-cvs) | ||
| 131 | |||
| 132 | |||
| 133 | ;; There is normally no need to alter the following variable, but if | ||
| 134 | ;; your site has installed CVS in a non-standard way you might have | ||
| 135 | ;; to change it. | ||
| 136 | |||
| 137 | (defvar cvs-bakprefix ".#" | ||
| 138 | "The prefix that CVS prepends to files when rcsmerge'ing.") | ||
| 139 | |||
| 140 | (easy-mmode-defmap cvs-filename-map | ||
| 141 | '(([(mouse-2)] . cvs-mode-find-file)) | ||
| 142 | "Local keymap for text properties of file names" | ||
| 143 | :inherit 'cvs-mode-map) | ||
| 144 | |||
| 145 | (easy-mmode-defmap cvs-status-map | ||
| 146 | '(([(mouse-2)] . cvs-mouse-toggle-mark)) | ||
| 147 | "Local keymap for text properties of status" | ||
| 148 | :inherit 'cvs-mode-map) | ||
| 149 | |||
| 150 | (easy-mmode-defmap cvs-dirname-map | ||
| 151 | '(([(mouse-2)] . cvs-mode-find-file)) | ||
| 152 | "Local keymap for text properties of directory names" | ||
| 153 | :inherit 'cvs-mode-map) | ||
| 154 | |||
| 155 | ;; Constructor: | ||
| 156 | |||
| 157 | (defstruct (cvs-fileinfo | ||
| 158 | (:constructor nil) | ||
| 159 | (:copier nil) | ||
| 160 | (:constructor -cvs-create-fileinfo (type dir file full-log | ||
| 161 | &key marked subtype | ||
| 162 | merge | ||
| 163 | base-rev | ||
| 164 | head-rev)) | ||
| 165 | (:conc-name cvs-fileinfo->)) | ||
| 166 | marked ;; t/nil. | ||
| 167 | type ;; See below | ||
| 168 | subtype ;; See below | ||
| 169 | dir ;; Relative directory the file resides in. | ||
| 170 | ;; (concat dir file) should give a valid path. | ||
| 171 | file ;; The file name sans the directory. | ||
| 172 | base-rev ;; During status: This is the revision that the | ||
| 173 | ;; working file is based on. | ||
| 174 | head-rev ;; During status: This is the highest revision in | ||
| 175 | ;; the repository. | ||
| 176 | merge ;; A cons cell containing the (ancestor . head) revisions | ||
| 177 | ;; of the merge that resulted in the current file. | ||
| 178 | ;;removed ;; t if the file no longer exists. | ||
| 179 | full-log ;; The output from cvs, unparsed. | ||
| 180 | ;;mod-time ;; Not used. | ||
| 181 | |||
| 182 | ;; In addition to the above, the following values can be extracted: | ||
| 183 | |||
| 184 | ;; handled ;; t if this file doesn't require further action. | ||
| 185 | ;; full-path ;; The complete relative filename. | ||
| 186 | ;; pp-name ;; The printed file name | ||
| 187 | ;; backup-file;; For MERGED and CONFLICT files after a \"cvs update\", | ||
| 188 | ;; this is a full path to the backup file where the | ||
| 189 | ;; untouched version resides. | ||
| 190 | |||
| 191 | ;; The meaning of the type field: | ||
| 192 | |||
| 193 | ;; Value ---Used by--- Explanation | ||
| 194 | ;; update status | ||
| 195 | ;; NEED-UPDATE x file needs update | ||
| 196 | ;; MODIFIED x x modified by you, unchanged in repository | ||
| 197 | ;; MERGED x x successful merge | ||
| 198 | ;; ADDED x x added by you, not yet committed | ||
| 199 | ;; MISSING x rm'd, but not yet `cvs remove'd | ||
| 200 | ;; REMOVED x x removed by you, not yet committed | ||
| 201 | ;; NEED-MERGE x need merge | ||
| 202 | ;; CONFLICT x conflict when merging | ||
| 203 | ;; ;;MOD-CONFLICT x removed locally, changed in repository. | ||
| 204 | ;; DIRCHANGE x x A change of directory. | ||
| 205 | ;; UNKNOWN x An unknown file. | ||
| 206 | ;; UP-TO-DATE x The file is up-to-date. | ||
| 207 | ;; UPDATED x x file copied from repository | ||
| 208 | ;; PATCHED x x diff applied from repository | ||
| 209 | ;; COMMITTED x x cvs commit'd | ||
| 210 | ;; DEAD An entry that should be removed | ||
| 211 | ;; MESSAGE x x This is a special fileinfo that is used | ||
| 212 | ;; to display a text that should be in | ||
| 213 | ;; full-log." | ||
| 214 | ;; TEMP A temporary message that should be removed | ||
| 215 | ;; HEADER A message that should stick at the top of the display | ||
| 216 | ;; FOOTER A message that should stick at the bottom of the display | ||
| 217 | ) | ||
| 218 | (defun cvs-create-fileinfo (type dir file msg &rest keys) | ||
| 219 | (cvs-check-fileinfo (apply #'-cvs-create-fileinfo type dir file msg keys))) | ||
| 220 | |||
| 221 | ;; Fake selectors: | ||
| 222 | |||
| 223 | (defun cvs-fileinfo->full-path (fileinfo) | ||
| 224 | "Return the full path for the file that is described in FILEINFO." | ||
| 225 | (let ((dir (cvs-fileinfo->dir fileinfo))) | ||
| 226 | (if (eq (cvs-fileinfo->type fileinfo) 'DIRCHANGE) | ||
| 227 | (if (string= dir "") "." (directory-file-name dir)) | ||
| 228 | ;; Here, I use `concat' rather than `expand-file-name' because I want | ||
| 229 | ;; the resulting path to stay relative if `dir' is relative. | ||
| 230 | ;; I could also use `expand-file-name' with `default-directory = ""' | ||
| 231 | (concat dir (cvs-fileinfo->file fileinfo))))) | ||
| 232 | |||
| 233 | (defun cvs-fileinfo->pp-name (fi) | ||
| 234 | "Return the filename of FI as it should be displayed." | ||
| 235 | (if cvs-display-full-path | ||
| 236 | (cvs-fileinfo->full-path fi) | ||
| 237 | (cvs-fileinfo->file fi))) | ||
| 238 | |||
| 239 | (defun cvs-fileinfo->backup-file (fileinfo) | ||
| 240 | "Construct the file name of the backup file for FILEINFO." | ||
| 241 | (let* ((dir (cvs-fileinfo->dir fileinfo)) | ||
| 242 | (file (cvs-fileinfo->file fileinfo)) | ||
| 243 | (default-directory (file-name-as-directory (expand-file-name dir))) | ||
| 244 | (files (directory-files "." nil | ||
| 245 | (concat "^" (regexp-quote cvs-bakprefix) | ||
| 246 | (regexp-quote file) "\\."))) | ||
| 247 | bf) | ||
| 248 | (dolist (f files bf) | ||
| 249 | (when (and (file-readable-p f) | ||
| 250 | (or (null bf) (file-newer-than-file-p f bf))) | ||
| 251 | (setq bf (concat dir f)))))) | ||
| 252 | |||
| 253 | ;; (defun cvs-fileinfo->handled (fileinfo) | ||
| 254 | ;; "Tell if this requires further action" | ||
| 255 | ;; (memq (cvs-fileinfo->type fileinfo) '(UP-TO-DATE DEAD))) | ||
| 256 | |||
| 257 | |||
| 258 | ;; Predicate: | ||
| 259 | |||
| 260 | (defun boolp (x) (or (eq t x) (null x))) | ||
| 261 | (defun cvs-check-fileinfo (fi) | ||
| 262 | "Check FI's conformance to some conventions." | ||
| 263 | (let ((check 'none) | ||
| 264 | (type (cvs-fileinfo->type fi)) | ||
| 265 | (subtype (cvs-fileinfo->subtype fi)) | ||
| 266 | (marked (cvs-fileinfo->marked fi)) | ||
| 267 | (dir (cvs-fileinfo->dir fi)) | ||
| 268 | (file (cvs-fileinfo->file fi)) | ||
| 269 | (base-rev (cvs-fileinfo->base-rev fi)) | ||
| 270 | (head-rev (cvs-fileinfo->head-rev fi)) | ||
| 271 | (full-log (cvs-fileinfo->full-log fi))) | ||
| 272 | (if (and (setq check 'marked) (boolp marked) | ||
| 273 | (setq check 'base-rev) (or (null base-rev) (stringp base-rev)) | ||
| 274 | (setq check 'head-rev) (or (null head-rev) (stringp head-rev)) | ||
| 275 | (setq check 'full-log) (stringp full-log) | ||
| 276 | (setq check 'dir) | ||
| 277 | (and (stringp dir) | ||
| 278 | (not (file-name-absolute-p dir)) | ||
| 279 | (or (string= dir "") | ||
| 280 | (string= dir (file-name-as-directory dir)))) | ||
| 281 | (setq check 'file) | ||
| 282 | (and (stringp file) | ||
| 283 | (string= file (file-name-nondirectory file))) | ||
| 284 | (setq check 'type) (symbolp type) | ||
| 285 | (setq check 'consistency) | ||
| 286 | (case type | ||
| 287 | (DIRCHANGE (and (null subtype) (string= "." file))) | ||
| 288 | ((NEED-UPDATE ADDED MISSING DEAD MODIFIED MESSAGE UP-TO-DATE | ||
| 289 | REMOVED NEED-MERGE CONFLICT UNKNOWN MESSAGE) | ||
| 290 | t))) | ||
| 291 | fi | ||
| 292 | (error "Invalid :%s in cvs-fileinfo %s" check fi)))) | ||
| 293 | |||
| 294 | |||
| 295 | ;;;; | ||
| 296 | ;;;; State table to indicate what you can do when. | ||
| 297 | ;;;; | ||
| 298 | |||
| 299 | (defconst cvs-states | ||
| 300 | `((NEED-UPDATE update diff) | ||
| 301 | (UP-TO-DATE update nil remove diff safe-rm revert) | ||
| 302 | (MODIFIED update commit undo remove diff merge diff-base) | ||
| 303 | (ADDED update commit remove) | ||
| 304 | (MISSING remove undo update safe-rm revert) | ||
| 305 | (REMOVED commit add undo safe-rm) | ||
| 306 | (NEED-MERGE update undo diff diff-base) | ||
| 307 | (CONFLICT merge remove undo commit diff diff-base) | ||
| 308 | (DIRCHANGE remove update diff ,(if cvs-allow-dir-commit 'commit) tag) | ||
| 309 | (UNKNOWN ignore add remove) | ||
| 310 | (DEAD ) | ||
| 311 | (MESSAGE)) | ||
| 312 | "Fileinfo state descriptions for pcl-cvs. | ||
| 313 | This is an assoc list. Each element consists of (STATE . FUNS) | ||
| 314 | - STATE (described in `cvs-create-fileinfo') is the key | ||
| 315 | - FUNS is the list of applicable operations. | ||
| 316 | The first one (if any) should be the \"default\" action. | ||
| 317 | Most of the actions have the obvious meaning. | ||
| 318 | `safe-rm' indicates that the file can be removed without losing | ||
| 319 | any information.") | ||
| 320 | |||
| 321 | ;;;; | ||
| 322 | ;;;; Utility functions | ||
| 323 | ;;;; | ||
| 324 | |||
| 325 | ;;---------- | ||
| 326 | (defun cvs-applicable-p (fi-or-type func) | ||
| 327 | "Check if FUNC is applicable to FI-OR-TYPE. | ||
| 328 | If FUNC is nil, always return t. | ||
| 329 | FI-OR-TYPE can either be a symbol (a fileinfo-type) or a fileinfo." | ||
| 330 | (let ((type (if (symbolp fi-or-type) fi-or-type | ||
| 331 | (cvs-fileinfo->type fi-or-type)))) | ||
| 332 | (and (not (eq type 'MESSAGE)) | ||
| 333 | (eq (car (memq func (cdr (assq type cvs-states)))) func)))) | ||
| 334 | |||
| 335 | ;; (defun cvs-default-action (fileinfo) | ||
| 336 | ;; "Return some kind of \"default\" action to be performed." | ||
| 337 | ;; (second (assq (cvs-fileinfo->type fileinfo) cvs-states))) | ||
| 338 | |||
| 339 | ;; fileinfo pretty-printers: | ||
| 340 | |||
| 341 | (defun cvs-add-face (str face &optional keymap) | ||
| 342 | (when cvs-highlight | ||
| 343 | (add-text-properties 0 (length str) | ||
| 344 | (list* 'face face | ||
| 345 | (when keymap | ||
| 346 | (list 'mouse-face 'highlight | ||
| 347 | 'local-map keymap))) | ||
| 348 | str)) | ||
| 349 | str) | ||
| 350 | |||
| 351 | ;;---------- | ||
| 352 | (defun cvs-fileinfo-pp (fileinfo) | ||
| 353 | "Pretty print FILEINFO. Insert a printed representation in current buffer. | ||
| 354 | For use by the cookie package." | ||
| 355 | (cvs-check-fileinfo fileinfo) | ||
| 356 | (let ((type (cvs-fileinfo->type fileinfo)) | ||
| 357 | (subtype (cvs-fileinfo->subtype fileinfo))) | ||
| 358 | (insert | ||
| 359 | (case type | ||
| 360 | (DIRCHANGE (concat "In directory " | ||
| 361 | (cvs-add-face (cvs-fileinfo->full-path fileinfo) | ||
| 362 | 'cvs-header-face cvs-dirname-map) | ||
| 363 | ":")) | ||
| 364 | (MESSAGE | ||
| 365 | (if (memq (cvs-fileinfo->subtype fileinfo) '(FOOTER HEADER)) | ||
| 366 | (cvs-fileinfo->full-log fileinfo) | ||
| 367 | (cvs-add-face (format "Message: %s" (cvs-fileinfo->full-log fileinfo)) | ||
| 368 | 'cvs-msg-face))) | ||
| 369 | (t | ||
| 370 | (let* ((status (if (cvs-fileinfo->marked fileinfo) | ||
| 371 | (cvs-add-face "*" 'cvs-marked-face) | ||
| 372 | " ")) | ||
| 373 | (file (cvs-add-face (cvs-fileinfo->pp-name fileinfo) | ||
| 374 | 'cvs-filename-face cvs-filename-map)) | ||
| 375 | (base (or (cvs-fileinfo->base-rev fileinfo) "")) | ||
| 376 | (head (cvs-fileinfo->head-rev fileinfo)) | ||
| 377 | (type | ||
| 378 | (let ((str (case type | ||
| 379 | ;;(MOD-CONFLICT "Not Removed") | ||
| 380 | (DEAD "") | ||
| 381 | (t (capitalize (symbol-name type))))) | ||
| 382 | (face (case type | ||
| 383 | (UP-TO-DATE 'cvs-handled-face) | ||
| 384 | (UNKNOWN 'cvs-unknown-face) | ||
| 385 | (t 'cvs-need-action-face)))) | ||
| 386 | (cvs-add-face str face cvs-status-map))) | ||
| 387 | (side (or | ||
| 388 | ;; maybe a subtype | ||
| 389 | (when subtype (downcase (symbol-name subtype))) | ||
| 390 | ;; or the head-rev | ||
| 391 | (when (and head (not (string= head base))) head) | ||
| 392 | ;; or nothing | ||
| 393 | "")) | ||
| 394 | ;; (action (cvs-add-face (case (cvs-default-action fileinfo) | ||
| 395 | ;; (commit "com") | ||
| 396 | ;; (update "upd") | ||
| 397 | ;; (undo "udo") | ||
| 398 | ;; (t " ")) | ||
| 399 | ;; 'cvs-action-face | ||
| 400 | ;; cvs-action-map)) | ||
| 401 | ) | ||
| 402 | (concat (cvs-string-fill side 11) " " | ||
| 403 | status " " | ||
| 404 | (cvs-string-fill type 11) " " | ||
| 405 | ;; action " " | ||
| 406 | (cvs-string-fill base 11) " " | ||
| 407 | file))))))) | ||
| 408 | ;; it seems that `format' removes text-properties. Too bad! | ||
| 409 | ;; (format "%-11s %s %-11s %-11s %s" | ||
| 410 | ;; side status type base file))))))) | ||
| 411 | |||
| 412 | |||
| 413 | (defun cvs-fileinfo-update (fi fi-new) | ||
| 414 | "Update FI with the information provided in FI-NEW." | ||
| 415 | (let ((type (cvs-fileinfo->type fi-new)) | ||
| 416 | (merge (cvs-fileinfo->merge fi-new))) | ||
| 417 | (setf (cvs-fileinfo->type fi) type) | ||
| 418 | (setf (cvs-fileinfo->subtype fi) (cvs-fileinfo->subtype fi-new)) | ||
| 419 | (setf (cvs-fileinfo->full-log fi) (cvs-fileinfo->full-log fi-new)) | ||
| 420 | (setf (cvs-fileinfo->base-rev fi) (cvs-fileinfo->base-rev fi-new)) | ||
| 421 | (setf (cvs-fileinfo->head-rev fi) (cvs-fileinfo->head-rev fi-new)) | ||
| 422 | (cond | ||
| 423 | (merge (setf (cvs-fileinfo->merge fi) merge)) | ||
| 424 | ((memq type '(UP-TO-DATE NEED-UPDATE)) | ||
| 425 | (setf (cvs-fileinfo->merge fi) nil))))) | ||
| 426 | |||
| 427 | ;;---------- | ||
| 428 | (defun cvs-fileinfo< (a b) | ||
| 429 | "Compare fileinfo A with fileinfo B and return t if A is `less'. | ||
| 430 | The ordering defined by this function is such that directories are | ||
| 431 | sorted alphabetically, and inside every directory the DIRCHANGE | ||
| 432 | fileinfo will appear first, followed by all files (alphabetically)." | ||
| 433 | (let ((subtypea (cvs-fileinfo->subtype a)) | ||
| 434 | (subtypeb (cvs-fileinfo->subtype b))) | ||
| 435 | (cond | ||
| 436 | ;; keep header and footer where they belong. Note: the order is important | ||
| 437 | ((eq subtypeb 'HEADER) nil) | ||
| 438 | ((eq subtypea 'HEADER) t) | ||
| 439 | ((eq subtypea 'FOOTER) nil) | ||
| 440 | ((eq subtypeb 'FOOTER) t) | ||
| 441 | |||
| 442 | ;; Sort according to directories. | ||
| 443 | ((string< (cvs-fileinfo->dir a) (cvs-fileinfo->dir b)) t) | ||
| 444 | ((not (string= (cvs-fileinfo->dir a) (cvs-fileinfo->dir b))) nil) | ||
| 445 | |||
| 446 | ;; The DIRCHANGE entry is always first within the directory. | ||
| 447 | ((eq (cvs-fileinfo->type b) 'DIRCHANGE) nil) | ||
| 448 | ((eq (cvs-fileinfo->type a) 'DIRCHANGE) t) | ||
| 449 | |||
| 450 | ;; All files are sorted by file name. | ||
| 451 | ((string< (cvs-fileinfo->file a) (cvs-fileinfo->file b)))))) | ||
| 452 | |||
| 453 | (provide 'pcvs-info) | ||
| 454 | |||
| 455 | ;;; pcl-cvs-info.el ends here | ||
diff --git a/lisp/pcvs-parse.el b/lisp/pcvs-parse.el new file mode 100644 index 00000000000..b65f8d2eb60 --- /dev/null +++ b/lisp/pcvs-parse.el | |||
| @@ -0,0 +1,478 @@ | |||
| 1 | ;;; pcvs-parse.el --- The CVS output parser | ||
| 2 | |||
| 3 | ;; Copyright (C) 1991-2000 Free Software Foundation, Inc. | ||
| 4 | |||
| 5 | ;; Author: Stefan Monnier <monnier@cs.yale.edu> | ||
| 6 | ;; Keywords: pcl-cvs | ||
| 7 | ;; Version: $Name: $ | ||
| 8 | ;; Revision: $Id: pcl-cvs-parse.el,v 1.41 2000/03/05 21:32:21 monnier Exp $ | ||
| 9 | |||
| 10 | ;; This file is part of GNU Emacs. | ||
| 11 | |||
| 12 | ;; GNU Emacs is free software; you can redistribute it and/or modify | ||
| 13 | ;; it under the terms of the GNU General Public License as published by | ||
| 14 | ;; the Free Software Foundation; either version 2, or (at your option) | ||
| 15 | ;; any later version. | ||
| 16 | |||
| 17 | ;; GNU Emacs is distributed in the hope that it will be useful, | ||
| 18 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 19 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 20 | ;; GNU General Public License for more details. | ||
| 21 | |||
| 22 | ;; You should have received a copy of the GNU General Public License | ||
| 23 | ;; along with GNU Emacs; see the file COPYING. If not, write to the | ||
| 24 | ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, | ||
| 25 | ;; Boston, MA 02111-1307, USA. | ||
| 26 | |||
| 27 | ;;; Commentary: | ||
| 28 | |||
| 29 | |||
| 30 | ;;; Code: | ||
| 31 | |||
| 32 | (eval-when-compile (require 'cl)) | ||
| 33 | |||
| 34 | (require 'pcvs-util) | ||
| 35 | (require 'pcvs-info) | ||
| 36 | |||
| 37 | ;; imported from pcvs.el | ||
| 38 | (defvar cvs-execute-single-dir) | ||
| 39 | |||
| 40 | ;; parse vars | ||
| 41 | |||
| 42 | (defcustom cvs-update-prog-output-skip-regexp "$" | ||
| 43 | "*A regexp that matches the end of the output from all cvs update programs. | ||
| 44 | That is, output from any programs that are run by CVS (by the flag -u | ||
| 45 | in the `modules' file - see cvs(5)) when `cvs update' is performed should | ||
| 46 | terminate with a line that this regexp matches. It is enough that | ||
| 47 | some part of the line is matched. | ||
| 48 | |||
| 49 | The default (a single $) fits programs without output." | ||
| 50 | :group 'pcl-cvs | ||
| 51 | :type '(regexp :value "$")) | ||
| 52 | |||
| 53 | (defcustom cvs-parse-ignored-messages | ||
| 54 | '("Executing ssh-askpass to query the password.*$" | ||
| 55 | ".*Remote host denied X11 forwarding.*$") | ||
| 56 | "*A list of regexps matching messages that should be ignored by the parser. | ||
| 57 | Each regexp should match a whole set of lines and should hence be terminated | ||
| 58 | by `$'." | ||
| 59 | :group 'pcl-cvs | ||
| 60 | :type '(repeat regexp)) | ||
| 61 | |||
| 62 | ;; a few more defvars just to shut up the compiler | ||
| 63 | (defvar cvs-start) | ||
| 64 | (defvar cvs-current-dir) | ||
| 65 | (defvar cvs-current-subdir) | ||
| 66 | (defvar dont-change-disc) | ||
| 67 | |||
| 68 | ;;;; The parser | ||
| 69 | |||
| 70 | (defconst cvs-parse-known-commands | ||
| 71 | '("status" "add" "commit" "update" "remove" "checkout" "ci") | ||
| 72 | "List of CVS commands whose output is understood by the parser.") | ||
| 73 | |||
| 74 | (defun cvs-parse-buffer (parse-spec dont-change-disc &optional subdir) | ||
| 75 | "Parse current buffer according to PARSE-SPEC. | ||
| 76 | PARSE-SPEC is a function of no argument advancing the point and returning | ||
| 77 | either a fileinfo or t (if the matched text should be ignored) or | ||
| 78 | nil if it didn't match anything. | ||
| 79 | DONT-CHANGE-DISC just indicates whether the command was changing the disc | ||
| 80 | or not (useful to tell the difference btween `cvs-examine' and `cvs-update' | ||
| 81 | ouytput. | ||
| 82 | The path names should be interpreted as relative to SUBDIR (defaults | ||
| 83 | to the `default-directory'). | ||
| 84 | Return a list of collected entries, or t if an error occured." | ||
| 85 | (goto-char (point-min)) | ||
| 86 | (let ((fileinfos ()) | ||
| 87 | (cvs-current-dir "") | ||
| 88 | (case-fold-search nil) | ||
| 89 | (cvs-current-subdir (or subdir ""))) | ||
| 90 | (while (not (or (eobp) (eq fileinfos t))) | ||
| 91 | (let ((ret (cvs-parse-run-table parse-spec))) | ||
| 92 | (cond | ||
| 93 | ;; it matched a known information message | ||
| 94 | ((cvs-fileinfo-p ret) (push ret fileinfos)) | ||
| 95 | ;; it didn't match anything at all (impossible) | ||
| 96 | ((and (consp ret) (cvs-fileinfo-p (car ret))) | ||
| 97 | (setq fileinfos (append ret fileinfos))) | ||
| 98 | ((null ret) (setq fileinfos t)) | ||
| 99 | ;; it matched something that should be ignored | ||
| 100 | (t nil)))) | ||
| 101 | (nreverse fileinfos))) | ||
| 102 | |||
| 103 | |||
| 104 | ;; All those parsing macros/functions should return a success indicator | ||
| 105 | (defsubst cvs-parse-msg () (buffer-substring cvs-start (1- (point)))) | ||
| 106 | |||
| 107 | ;;(defsubst COLLECT (exp) (push exp *result*)) | ||
| 108 | ;;(defsubst PROG (e) t) | ||
| 109 | ;;(defmacro SEQ (&rest seqs) (cons 'and seqs)) | ||
| 110 | |||
| 111 | (defmacro cvs-match (re &rest matches) | ||
| 112 | "Try to match RE and extract submatches. | ||
| 113 | If RE matches, advance the point until the line after the match and | ||
| 114 | then assign the variables as specified in MATCHES (via `setq')." | ||
| 115 | (cons 'cvs-do-match | ||
| 116 | (cons re (mapcar (lambda (match) | ||
| 117 | `(cons ',(first match) ,(second match))) | ||
| 118 | matches)))) | ||
| 119 | |||
| 120 | (defun cvs-do-match (re &rest matches) | ||
| 121 | "Internal function for the `cvs-match' macro. | ||
| 122 | Match RE and if successful, execute MATCHES." | ||
| 123 | ;; Is it a match? | ||
| 124 | (when (looking-at re) | ||
| 125 | (goto-char (match-end 0)) | ||
| 126 | ;; Skip the newline (unless we already are at the end of the buffer). | ||
| 127 | (when (and (eolp) (< (point) (point-max))) (forward-char)) | ||
| 128 | ;; assign the matches | ||
| 129 | (dolist (match matches t) | ||
| 130 | (let ((val (cdr match))) | ||
| 131 | (set (car match) (if (integerp val) (match-string val) val)))))) | ||
| 132 | |||
| 133 | (defmacro cvs-or (&rest alts) | ||
| 134 | "Try each one of the ALTS alternatives until one matches." | ||
| 135 | `(let ((-cvs-parse-point (point))) | ||
| 136 | ,(cons 'or | ||
| 137 | (mapcar (lambda (es) | ||
| 138 | `(or ,es (ignore (goto-char -cvs-parse-point)))) | ||
| 139 | alts)))) | ||
| 140 | (def-edebug-spec cvs-or t) | ||
| 141 | |||
| 142 | ;; This is how parser tables should be executed | ||
| 143 | (defun cvs-parse-run-table (parse-spec) | ||
| 144 | "Run PARSE-SPEC and provide sensible default behavior." | ||
| 145 | (unless (bolp) (forward-line 1)) ;this should never be needed | ||
| 146 | (let ((cvs-start (point))) | ||
| 147 | (cvs-or | ||
| 148 | (funcall parse-spec) | ||
| 149 | |||
| 150 | (dolist (re cvs-parse-ignored-messages) | ||
| 151 | (when (cvs-match re) (return t))) | ||
| 152 | |||
| 153 | ;; This is a parse error. Create a message-type fileinfo. | ||
| 154 | (and | ||
| 155 | (cvs-match ".*$") | ||
| 156 | (cvs-create-fileinfo 'MESSAGE cvs-current-dir " " | ||
| 157 | (concat " Parser Error: '" (cvs-parse-msg) "'") | ||
| 158 | :subtype 'ERROR))))) | ||
| 159 | |||
| 160 | |||
| 161 | (defun cvs-parsed-fileinfo (type path &optional directory &rest keys) | ||
| 162 | "Create a fileinfo. | ||
| 163 | TYPE can either be a type symbol or a cons of the form (TYPE . SUBTYPE). | ||
| 164 | PATH is the filename. | ||
| 165 | DIRECTORY influences the way PATH is interpreted: | ||
| 166 | - if it's a string, it denotes the directory in which PATH (which should then be | ||
| 167 | a plain file name with no directory component) resides. | ||
| 168 | - if it's nil, the PATH should not be trusted: if it has a directory | ||
| 169 | component, use it, else, assume it is relative to the current directory. | ||
| 170 | - else, the PATH should be trusted to be relative to the root | ||
| 171 | directory (i.e. if there is no directory component, it means the file | ||
| 172 | is inside the main directory). | ||
| 173 | The remaining KEYS are passed directly to `cvs-create-fileinfo'." | ||
| 174 | (let ((dir directory) | ||
| 175 | (file path)) | ||
| 176 | ;; only trust the directory if it's a string | ||
| 177 | (unless (stringp directory) | ||
| 178 | ;; else, if the directory is true, the path should be trusted | ||
| 179 | (setq dir (or (file-name-directory path) (if directory ""))) | ||
| 180 | (setq file (file-name-nondirectory path))) | ||
| 181 | |||
| 182 | (let ((type (if (consp type) (car type) type)) | ||
| 183 | (subtype (if (consp type) (cdr type)))) | ||
| 184 | (when dir (setq cvs-current-dir dir)) | ||
| 185 | (apply 'cvs-create-fileinfo type | ||
| 186 | (concat cvs-current-subdir (or dir cvs-current-dir)) | ||
| 187 | file (cvs-parse-msg) :subtype subtype keys)))) | ||
| 188 | |||
| 189 | |||
| 190 | ;;;; CVS Process Parser Tables: | ||
| 191 | ;;;; | ||
| 192 | ;;;; The table for status and update could actually be merged since they | ||
| 193 | ;;;; don't conflict. But they don't overlap much either. | ||
| 194 | |||
| 195 | (defun cvs-parse-table () | ||
| 196 | "Table of message objects for `cvs-parse-process'." | ||
| 197 | (let (c file dir path type base-rev subtype) | ||
| 198 | (cvs-or | ||
| 199 | |||
| 200 | (cvs-parse-status) | ||
| 201 | (cvs-parse-merge) | ||
| 202 | (cvs-parse-commit) | ||
| 203 | |||
| 204 | ;; this is not necessary because the fileinfo merging will remove | ||
| 205 | ;; such duplicate info and luckily the second info is the one we want. | ||
| 206 | ;; (and (cvs-match "M \\(.*\\)$" (path 1)) | ||
| 207 | ;; (cvs-parse-merge path)) | ||
| 208 | |||
| 209 | ;; Normal file state indicator. | ||
| 210 | (and | ||
| 211 | (cvs-match "\\([MARCUPNJ?]\\) \\(.*\\)$" (c 1) (path 2)) | ||
| 212 | ;; M: The file is modified by the user, and untouched in the repository. | ||
| 213 | ;; A: The file is "cvs add"ed, but not "cvs ci"ed. | ||
| 214 | ;; R: The file is "cvs remove"ed, but not "cvs ci"ed. | ||
| 215 | ;; C: Conflict | ||
| 216 | ;; U: The file is copied from the repository. | ||
| 217 | ;; P: The file was patched from the repository. | ||
| 218 | ;; ?: Unknown file. | ||
| 219 | (let ((code (aref c 0))) | ||
| 220 | (cvs-parsed-fileinfo (case code | ||
| 221 | (?M 'MODIFIED) | ||
| 222 | (?A 'ADDED) | ||
| 223 | (?R 'REMOVED) | ||
| 224 | (?? 'UNKNOWN) | ||
| 225 | (?C 'CONFLICT) ;(if dont-change-disc 'NEED-MERGE | ||
| 226 | (?J 'NEED-MERGE) ;not supported by standard CVS | ||
| 227 | ((?U ?P) | ||
| 228 | (if dont-change-disc | ||
| 229 | 'NEED-UPDATE | ||
| 230 | (cons 'UP-TO-DATE | ||
| 231 | (if (eq code ?U) 'UPDATED 'PATCHED))))) | ||
| 232 | path 'trust))) | ||
| 233 | |||
| 234 | (and | ||
| 235 | (cvs-match "pcl-cvs: descending directory \\(.*\\)$" (dir 1)) | ||
| 236 | (setq cvs-current-subdir dir)) | ||
| 237 | |||
| 238 | ;; A special cvs message | ||
| 239 | (and | ||
| 240 | (cvs-match "cvs[.ex]* [a-z]+: ") | ||
| 241 | (cvs-or | ||
| 242 | |||
| 243 | ;; CVS is descending a subdirectory | ||
| 244 | ;; (status says `examining' while update says `updating') | ||
| 245 | (and | ||
| 246 | (cvs-match "\\(Examining\\|Updating\\) \\(.*\\)$" (dir 2)) | ||
| 247 | (let ((dir (if (string= "." dir) "" (file-name-as-directory dir)))) | ||
| 248 | (cvs-parsed-fileinfo 'DIRCHANGE "." dir))) | ||
| 249 | |||
| 250 | ;; [-n update] A new (or pruned) directory appeared but isn't traversed | ||
| 251 | (and | ||
| 252 | (cvs-match "New directory `\\(.*\\)' -- ignored$" (dir 1)) | ||
| 253 | (cvs-parsed-fileinfo 'MESSAGE " " (file-name-as-directory dir))) | ||
| 254 | |||
| 255 | ;; File removed, since it is removed (by third party) in repository. | ||
| 256 | (and | ||
| 257 | (cvs-or | ||
| 258 | (cvs-match "warning: \\(.*\\) is not (any longer) pertinent$" (file 1)) | ||
| 259 | (cvs-match "\\(.*\\) is no longer in the repository$" (file 1))) | ||
| 260 | (cvs-parsed-fileinfo 'DEAD file)) | ||
| 261 | |||
| 262 | ;; [add] | ||
| 263 | (and | ||
| 264 | (cvs-or | ||
| 265 | (cvs-match "scheduling file `\\(.*\\)' for addition.*$" (path 1)) | ||
| 266 | (cvs-match "re-adding file \\(.*\\) (in place of .*)$" (path 1))) | ||
| 267 | (cvs-parsed-fileinfo 'ADDED path)) | ||
| 268 | |||
| 269 | ;; [add] this will also show up as a `U <file>' | ||
| 270 | (and | ||
| 271 | (cvs-match "\\(.*\\), version \\(.*\\), resurrected$" | ||
| 272 | (path 1) (base-rev 2)) | ||
| 273 | (cvs-parsed-fileinfo '(UP-TO-DATE . RESURRECTED) path nil | ||
| 274 | :base-rev base-rev)) | ||
| 275 | |||
| 276 | ;; [remove] | ||
| 277 | (and | ||
| 278 | (cvs-match "removed `\\(.*\\)'$" (path 1)) | ||
| 279 | (cvs-parsed-fileinfo 'DEAD path)) | ||
| 280 | |||
| 281 | ;; [remove,merge] | ||
| 282 | (and | ||
| 283 | (cvs-match "scheduling `\\(.*\\)' for removal$" (file 1)) | ||
| 284 | (cvs-parsed-fileinfo 'REMOVED file)) | ||
| 285 | |||
| 286 | ;; [update] File removed by you, but not cvs rm'd | ||
| 287 | (and | ||
| 288 | (cvs-match "warning: \\(.*\\) was lost$" (path 1)) | ||
| 289 | (cvs-match (concat "U " (regexp-quote path) "$")) | ||
| 290 | (cvs-parsed-fileinfo (if dont-change-disc | ||
| 291 | 'MISSING | ||
| 292 | '(UP-TO-DATE . UPDATED)) | ||
| 293 | path)) | ||
| 294 | |||
| 295 | ;; Mode conflicts (rather than contents) | ||
| 296 | (and | ||
| 297 | (cvs-match "conflict: ") | ||
| 298 | (cvs-or | ||
| 299 | (cvs-match "removed \\(.*\\) was modified by second party$" | ||
| 300 | (path 1) (subtype 'REMOVED)) | ||
| 301 | (cvs-match "\\(.*\\) created independently by second party$" | ||
| 302 | (path 1) (subtype 'ADDED)) | ||
| 303 | (cvs-match "\\(.*\\) is modified but no longer in the repository$" | ||
| 304 | (path 1) (subtype 'MODIFIED))) | ||
| 305 | (cvs-match (concat "C " (regexp-quote path))) | ||
| 306 | (cvs-parsed-fileinfo (cons 'CONFLICT subtype) path)) | ||
| 307 | |||
| 308 | ;; Messages that should be shown to the user | ||
| 309 | (and | ||
| 310 | (cvs-or | ||
| 311 | (cvs-match "move away \\(.*\\); it is in the way$" (file 1)) | ||
| 312 | (cvs-match "warning: new-born \\(.*\\) has disappeared$" (file 1)) | ||
| 313 | (cvs-match "sticky tag .* for file `\\(.*\\)' is not a branch$" | ||
| 314 | (file 1))) | ||
| 315 | (cvs-parsed-fileinfo 'MESSAGE file)) | ||
| 316 | |||
| 317 | ;; File unknown. | ||
| 318 | (and (cvs-match "use `.+ add' to create an entry for \\(.*\\)$" (path 1)) | ||
| 319 | (cvs-parsed-fileinfo 'UNKNOWN path)) | ||
| 320 | |||
| 321 | ;; We use cvs-execute-multi-dir but cvs can't handle it | ||
| 322 | ;; Probably because the cvs-client can but the cvs-server can't | ||
| 323 | (and (cvs-match ".* files with '?/'? in their name.*$") | ||
| 324 | (not cvs-execute-single-dir) | ||
| 325 | (setq cvs-execute-single-dir t) | ||
| 326 | (cvs-create-fileinfo | ||
| 327 | 'MESSAGE "" " " | ||
| 328 | "*** Add (setq cvs-execute-single-dir t) to your .emacs *** | ||
| 329 | See the FAQ file or the variable's documentation for more info.")) | ||
| 330 | |||
| 331 | ;; Cvs waits for a lock. Ignored: already handled by the process filter | ||
| 332 | (cvs-match "\\[..:..:..\\] \\(waiting for\\|obtained\\) .*lock in .*$") | ||
| 333 | ;; File you removed still exists. Ignore (will be noted as removed). | ||
| 334 | (cvs-match ".* should be removed and is still there$") | ||
| 335 | ;; just a note | ||
| 336 | (cvs-match "use '.+ commit' to \\sw+ th\\sw+ files? permanently$") | ||
| 337 | ;; [add,status] followed by a more complete status description anyway | ||
| 338 | (cvs-match "nothing known about .*$") | ||
| 339 | ;; [update] problem with patch | ||
| 340 | (cvs-match "checksum failure after patch to .*; will refetch$") | ||
| 341 | (cvs-match "refetching unpatchable files$") | ||
| 342 | ;; [commit] | ||
| 343 | (cvs-match "Rebuilding administrative file database$") | ||
| 344 | |||
| 345 | ;; CVS is running a *info program. | ||
| 346 | (and | ||
| 347 | (cvs-match "Executing.*$") | ||
| 348 | ;; Skip by any output the program may generate to stdout. | ||
| 349 | ;; Note that pcl-cvs will get seriously confused if the | ||
| 350 | ;; program prints anything to stderr. | ||
| 351 | (re-search-forward cvs-update-prog-output-skip-regexp)))) | ||
| 352 | |||
| 353 | (and | ||
| 354 | (cvs-match "cvs[.ex]* \\[[a-z]+ aborted\\]:.*$") | ||
| 355 | (cvs-parsed-fileinfo 'MESSAGE "")) | ||
| 356 | |||
| 357 | ;; sadly you can't do much with these since the path is in the repository | ||
| 358 | (cvs-match "Directory .* added to the repository$") | ||
| 359 | ))) | ||
| 360 | |||
| 361 | |||
| 362 | (defun cvs-parse-merge () | ||
| 363 | (let (path base-rev head-rev handled type) | ||
| 364 | ;; A merge (maybe with a conflict). | ||
| 365 | (and | ||
| 366 | (cvs-match "RCS file: .*$") | ||
| 367 | ;; Squirrel away info about the files that were retrieved for merging | ||
| 368 | (cvs-match "retrieving revision \\([0-9.]+\\)$" (base-rev 1)) | ||
| 369 | (cvs-match "retrieving revision \\([0-9.]+\\)$" (head-rev 1)) | ||
| 370 | (cvs-match "Merging differences between [0-9.]+ and [0-9.]+ into \\(.*\\)$" | ||
| 371 | (path 1)) | ||
| 372 | |||
| 373 | ;; eat up potential conflict warnings | ||
| 374 | (cvs-or (cvs-match "\\(rcs\\)?merge:?\\( warning\\)?: \\(overlaps\\|conflicts\\) \\(or other problems \\)?during merge$" (type 'CONFLICT)) t) | ||
| 375 | (cvs-or | ||
| 376 | (and | ||
| 377 | (cvs-match "cvs[.ex]* [a-z]+: ") | ||
| 378 | (cvs-or | ||
| 379 | (cvs-match "conflicts found in \\(.*\\)$" (path 1) (type 'CONFLICT)) | ||
| 380 | (cvs-match "could not merge .*$") | ||
| 381 | (cvs-match "restoring \\(.*\\) from backup file .*$" (path 1)))) | ||
| 382 | t) | ||
| 383 | |||
| 384 | ;; Is it a succesful merge? | ||
| 385 | ;; Figure out result of merging (ie, was there a conflict?) | ||
| 386 | (let ((qfile (regexp-quote path))) | ||
| 387 | (cvs-or | ||
| 388 | ;; Conflict | ||
| 389 | (and | ||
| 390 | (cvs-match (concat "C \\(.*" qfile "\\)$") (path 1) (type 'CONFLICT)) | ||
| 391 | ;; C might be followed by a "suprious" U for non-mergeable files | ||
| 392 | (cvs-or (cvs-match (concat "U \\(.*" qfile "\\)$")) t)) | ||
| 393 | ;; Successful merge | ||
| 394 | (cvs-match (concat "M \\(.*" qfile "\\)$") (path 1)) | ||
| 395 | ;; The file already contained the modifications | ||
| 396 | (cvs-match (concat "^\\(.*" qfile | ||
| 397 | "\\) already contains the differences between .*$") | ||
| 398 | (path 1) (type '(UP-TO-DATE . MERGED))) | ||
| 399 | t) | ||
| 400 | (cvs-parsed-fileinfo (if dont-change-disc 'NEED-MERGE | ||
| 401 | (or type '(MODIFIED . MERGED))) path nil | ||
| 402 | :merge (cons base-rev head-rev)))))) | ||
| 403 | |||
| 404 | (defun cvs-parse-status () | ||
| 405 | (let (nofile path base-rev head-rev type) | ||
| 406 | (and | ||
| 407 | (cvs-match | ||
| 408 | "===================================================================$") | ||
| 409 | (cvs-match "File: \\(no file \\)?\\(.*[^ \t]\\)[ \t]+Status: " | ||
| 410 | (nofile 1) (path 2)) | ||
| 411 | (cvs-or | ||
| 412 | (cvs-match "Needs \\(Checkout\\|Patch\\)$" | ||
| 413 | (type (if nofile 'MISSING 'NEED-UPDATE))) | ||
| 414 | (cvs-match "Up-to-date$" | ||
| 415 | (type (if nofile '(UP-TO-DATE . REMOVED) 'UP-TO-DATE))) | ||
| 416 | (cvs-match ".*[Cc]onflict.*$" (type 'CONFLICT)) | ||
| 417 | (cvs-match "Locally Added$" (type 'ADDED)) | ||
| 418 | (cvs-match "Locally Removed$" (type 'REMOVED)) | ||
| 419 | (cvs-match "Locally Modified$" (type 'MODIFIED)) | ||
| 420 | (cvs-match "Needs Merge$" (type 'NEED-MERGE)) | ||
| 421 | (cvs-match "Unknown$" (type 'UNKNOWN))) | ||
| 422 | (cvs-match "$") | ||
| 423 | (cvs-or | ||
| 424 | (cvs-match " *Version:[ \t]*\\([0-9.]+\\).*$" (base-rev 1)) | ||
| 425 | ;; NOTE: there's no date on the end of the following for server mode... | ||
| 426 | (cvs-match " *Working revision:[ \t]*-?\\([0-9.]+\\).*$" (base-rev 1)) | ||
| 427 | ;; Let's not get all worked up if the format changes a bit | ||
| 428 | (cvs-match " *Working revision:.*$")) | ||
| 429 | (cvs-or | ||
| 430 | (cvs-match " *RCS Version:[ \t]*\\([0-9.]+\\)[ \t]*.*$" (head-rev 1)) | ||
| 431 | (cvs-match " *Repository revision:[ \t]*\\([0-9.]+\\)[ \t]*\\(.*\\)$" | ||
| 432 | (head-rev 1)) | ||
| 433 | (cvs-match " *Repository revision:.*")) | ||
| 434 | (cvs-or | ||
| 435 | (and;;sometimes those fields are missing | ||
| 436 | (cvs-match " *Sticky Tag:[ \t]*\\(.*\\)$") ; FIXME: use it | ||
| 437 | (cvs-match " *Sticky Date:[ \t]*\\(.*\\)$") ; FIXME: use it | ||
| 438 | (cvs-match " *Sticky Options:[ \t]*\\(.*\\)$")) ; FIXME: use it | ||
| 439 | t) | ||
| 440 | (cvs-match "$") | ||
| 441 | ;; ignore the tags-listing in the case of `status -v' | ||
| 442 | (cvs-or (cvs-match " *Existing Tags:\n\\(\t.*\n\\)*$") t) | ||
| 443 | (cvs-parsed-fileinfo type path nil | ||
| 444 | :base-rev base-rev | ||
| 445 | :head-rev head-rev)))) | ||
| 446 | |||
| 447 | (defun cvs-parse-commit () | ||
| 448 | (let (path base-rev subtype) | ||
| 449 | (cvs-or | ||
| 450 | |||
| 451 | (and | ||
| 452 | (cvs-match "\\(Checking in\\|Removing\\) \\(.*\\);$" (path 2)) | ||
| 453 | (cvs-match ".*,v <-- .*$") | ||
| 454 | (cvs-or | ||
| 455 | ;; deletion | ||
| 456 | (cvs-match "new revision: delete; previous revision: \\([0-9.]*\\)$" | ||
| 457 | (subtype 'REMOVED) (base-rev 1)) | ||
| 458 | ;; addition | ||
| 459 | (cvs-match "initial revision: \\([0-9.]*\\)$" | ||
| 460 | (subtype 'ADDED) (base-rev 1)) | ||
| 461 | ;; update | ||
| 462 | (cvs-match "new revision: \\([0-9.]*\\); previous revision: .*$" | ||
| 463 | (subtype 'COMMITTED) (base-rev 1))) | ||
| 464 | (cvs-match "done$") | ||
| 465 | ;; it's important here not to rely on the default directory management | ||
| 466 | ;; because `cvs commit' might begin by a series of Examining messages | ||
| 467 | ;; so the processing of the actual checkin messages might begin with | ||
| 468 | ;; a `current-dir' set to something different from "" | ||
| 469 | (cvs-parsed-fileinfo (cons 'UP-TO-DATE subtype) path 'trust | ||
| 470 | :base-rev base-rev)) | ||
| 471 | |||
| 472 | ;; useless message added before the actual addition: ignored | ||
| 473 | (cvs-match "RCS file: .*\ndone$")))) | ||
| 474 | |||
| 475 | |||
| 476 | (provide 'pcvs-parse) | ||
| 477 | |||
| 478 | ;;; pcl-cvs-parse.el ends here | ||
diff --git a/lisp/pcvs-util.el b/lisp/pcvs-util.el new file mode 100644 index 00000000000..9763fd82566 --- /dev/null +++ b/lisp/pcvs-util.el | |||
| @@ -0,0 +1,381 @@ | |||
| 1 | ;;; pcvs-util.el --- Utitlity functions for pcl-cvs | ||
| 2 | |||
| 3 | ;; Copyright (C) 1998-2000 Free Software Foundation, Inc. | ||
| 4 | |||
| 5 | ;; Author: Stefan Monnier <monnier@cs.yale.edu> | ||
| 6 | ;; Keywords: pcl-cvs | ||
| 7 | ;; Version: $Name: $ | ||
| 8 | ;; Revision: $Id: pcl-cvs-util.el,v 1.26 2000/03/05 21:32:21 monnier Exp $ | ||
| 9 | |||
| 10 | ;; This file is part of GNU Emacs. | ||
| 11 | |||
| 12 | ;; GNU Emacs is free software; you can redistribute it and/or modify | ||
| 13 | ;; it under the terms of the GNU General Public License as published by | ||
| 14 | ;; the Free Software Foundation; either version 2, or (at your option) | ||
| 15 | ;; any later version. | ||
| 16 | |||
| 17 | ;; GNU Emacs is distributed in the hope that it will be useful, | ||
| 18 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 19 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 20 | ;; GNU General Public License for more details. | ||
| 21 | |||
| 22 | ;; You should have received a copy of the GNU General Public License | ||
| 23 | ;; along with GNU Emacs; see the file COPYING. If not, write to the | ||
| 24 | ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, | ||
| 25 | ;; Boston, MA 02111-1307, USA. | ||
| 26 | |||
| 27 | ;;; Commentary: | ||
| 28 | |||
| 29 | |||
| 30 | ;;; Code: | ||
| 31 | |||
| 32 | (eval-when-compile (require 'cl)) | ||
| 33 | |||
| 34 | ;;;; | ||
| 35 | ;;;; list processing | ||
| 36 | ;;;l | ||
| 37 | |||
| 38 | (defsubst cvs-car (x) (if (consp x) (car x) x)) | ||
| 39 | (defalias 'cvs-cdr 'cdr-safe) | ||
| 40 | (defsubst cvs-append (&rest xs) | ||
| 41 | (apply 'append (mapcar (lambda (x) (if (listp x) x (list x))) xs))) | ||
| 42 | |||
| 43 | (defsubst cvs-every (-cvs-every-f -cvs-every-l) | ||
| 44 | (while (consp -cvs-every-l) | ||
| 45 | (unless (funcall -cvs-every-f (pop -cvs-every-l)) | ||
| 46 | (setq -cvs-every-l t))) | ||
| 47 | (not -cvs-every-l)) | ||
| 48 | |||
| 49 | (defun cvs-union (xs ys) | ||
| 50 | (let ((zs ys)) | ||
| 51 | (dolist (x xs zs) | ||
| 52 | (unless (member x ys) (push x zs))))) | ||
| 53 | |||
| 54 | |||
| 55 | (defun cvs-map (-cvs-map-f &rest -cvs-map-ls) | ||
| 56 | (unless (cvs-every 'null -cvs-map-ls) | ||
| 57 | (cons (apply -cvs-map-f (mapcar 'car -cvs-map-ls)) | ||
| 58 | (apply 'cvs-map -cvs-map-f (mapcar 'cdr -cvs-map-ls))))) | ||
| 59 | |||
| 60 | (defun cvs-first (l &optional n) | ||
| 61 | (if (null n) (car l) | ||
| 62 | (when l | ||
| 63 | (let* ((nl (list (pop l))) | ||
| 64 | (ret nl)) | ||
| 65 | (while (and l (> n 1)) | ||
| 66 | (setcdr nl (list (pop l))) | ||
| 67 | (setq nl (cdr nl)) | ||
| 68 | (decf n)) | ||
| 69 | ret)))) | ||
| 70 | |||
| 71 | (defun cvs-partition (p l) | ||
| 72 | "Partition a list L into two lists based on predicate P. | ||
| 73 | The function returns a `cons' cell where the `car' contains | ||
| 74 | elements of L for which P is true while the `cdr' contains | ||
| 75 | the other elements. The ordering among elements is maintained." | ||
| 76 | (let (car cdr) | ||
| 77 | (dolist (x l) | ||
| 78 | (if (funcall p x) (push x car) (push x cdr))) | ||
| 79 | (cons (nreverse car) (nreverse cdr)))) | ||
| 80 | |||
| 81 | ;;;; | ||
| 82 | ;;;; frame, window, buffer handling | ||
| 83 | ;;;; | ||
| 84 | |||
| 85 | (defun cvs-pop-to-buffer-same-frame (buf) | ||
| 86 | "Pop to BUF like `pop-to-buffer' but staying on the same frame. | ||
| 87 | If `pop-to-buffer' would have opened a new frame, this function would | ||
| 88 | try to split the a new window instead." | ||
| 89 | (let ((pop-up-windows (or pop-up-windows pop-up-frames)) | ||
| 90 | (pop-up-frames nil)) | ||
| 91 | (or (let ((buf (get-buffer-window buf))) (and buf (select-window buf))) | ||
| 92 | (and pop-up-windows | ||
| 93 | (ignore-errors (select-window (split-window-vertically))) | ||
| 94 | (switch-to-buffer buf)) | ||
| 95 | (pop-to-buffer (current-buffer))))) | ||
| 96 | |||
| 97 | (defun cvs-bury-buffer (buf &optional mainbuf) | ||
| 98 | "Hide the buffer BUF that was temporarily popped up. | ||
| 99 | BUF is assumed to be a temporary buffer used from the buffer MAINBUF." | ||
| 100 | (interactive (list (current-buffer))) | ||
| 101 | (save-current-buffer | ||
| 102 | (let ((win (if (eq buf (window-buffer (selected-window))) (selected-window) | ||
| 103 | (get-buffer-window buf t)))) | ||
| 104 | (when win | ||
| 105 | (if (window-dedicated-p win) | ||
| 106 | (condition-case () | ||
| 107 | (delete-window win) | ||
| 108 | (error (iconify-frame (window-frame win)))) | ||
| 109 | (if (and mainbuf (get-buffer-window mainbuf)) | ||
| 110 | (delete-window win))))) | ||
| 111 | (with-current-buffer buf | ||
| 112 | (bury-buffer (unless (and (eq buf (window-buffer (selected-window))) | ||
| 113 | (not (window-dedicated-p (selected-window)))) | ||
| 114 | buf))) | ||
| 115 | (when mainbuf | ||
| 116 | (let ((mainwin (or (get-buffer-window mainbuf) | ||
| 117 | (get-buffer-window mainbuf 'visible)))) | ||
| 118 | (when mainwin (select-window mainwin)))))) | ||
| 119 | |||
| 120 | (defun cvs-get-buffer-create (name &optional noreuse) | ||
| 121 | "Create a buffer NAME unless such a buffer already exists. | ||
| 122 | If the NAME looks like an absolute file name, the buffer will be created | ||
| 123 | with `create-file-buffer' and will probably get another name than NAME. | ||
| 124 | In such a case, the search for another buffer with the same name doesn't | ||
| 125 | use the buffer name but the buffer's `list-buffers-directory' variable. | ||
| 126 | If NOREUSE is non-nil, always return a new buffer." | ||
| 127 | (or (and (not (file-name-absolute-p name)) (get-buffer-create name)) | ||
| 128 | (unless noreuse | ||
| 129 | (dolist (buf (buffer-list)) | ||
| 130 | (with-current-buffer buf | ||
| 131 | (when (equal name list-buffers-directory) | ||
| 132 | (return buf))))) | ||
| 133 | (with-current-buffer (create-file-buffer name) | ||
| 134 | (set (make-local-variable 'list-buffers-directory) name) | ||
| 135 | (current-buffer)))) | ||
| 136 | |||
| 137 | ;;;; | ||
| 138 | ;;;; string processing | ||
| 139 | ;;;; | ||
| 140 | |||
| 141 | (defun cvs-file-to-string (file &optional oneline args) | ||
| 142 | "Read the content of FILE and return it as a string. | ||
| 143 | If ONELINE is t, only the first line (no \\n) will be returned. | ||
| 144 | If ARGS is non-nil, the file will be executed with ARGS as its | ||
| 145 | arguments. If ARGS is not a list, no argument will be passed." | ||
| 146 | (with-temp-buffer | ||
| 147 | (condition-case nil | ||
| 148 | (progn | ||
| 149 | (if args | ||
| 150 | (apply 'call-process | ||
| 151 | file nil t nil (when (listp args) args)) | ||
| 152 | (insert-file-contents file)) | ||
| 153 | (buffer-substring (point-min) | ||
| 154 | (if oneline | ||
| 155 | (progn (goto-char (point-min)) (end-of-line) (point)) | ||
| 156 | (point-max)))) | ||
| 157 | (file-error nil)))) | ||
| 158 | |||
| 159 | (defun cvs-string-prefix-p (str1 str2) | ||
| 160 | "Tell whether STR1 is a prefix of STR2." | ||
| 161 | (let ((length1 (length str1))) | ||
| 162 | (and (>= (length str2) length1) | ||
| 163 | (string= str1 (substring str2 0 length1))))) | ||
| 164 | |||
| 165 | ;; (string->strings (strings->string X)) == X | ||
| 166 | (defun cvs-strings->string (strings &optional separator) | ||
| 167 | "Concatenate the STRINGS, adding the SEPARATOR (default \" \"). | ||
| 168 | This tries to quote the strings to avoid ambiguity such that | ||
| 169 | (cvs-string->strings (cvs-strings->string strs)) == strs | ||
| 170 | Only some SEPARATOR will work properly." | ||
| 171 | (let ((sep (or separator " "))) | ||
| 172 | (mapconcat | ||
| 173 | (lambda (str) | ||
| 174 | (if (string-match "[\\\"]" str) | ||
| 175 | (concat "\"" (replace-regexps-in-string "[\\\"]" "\\\\\\&" str) "\"") | ||
| 176 | str)) | ||
| 177 | strings sep))) | ||
| 178 | |||
| 179 | ;; (string->strings (strings->string X)) == X | ||
| 180 | (defun cvs-string->strings (string &optional separator) | ||
| 181 | "Split the STRING into a list of strings. | ||
| 182 | It understands elisp style quoting within STRING such that | ||
| 183 | (cvs-string->strings (cvs-strings->string strs)) == strs | ||
| 184 | The SEPARATOR regexp defaults to \"\\s-+\"." | ||
| 185 | (let ((sep (or separator "\\s-+")) | ||
| 186 | (i (string-match "[\"]" string))) | ||
| 187 | (if (null i) (split-string string sep) ; no quoting: easy | ||
| 188 | (append (unless (eq i 0) (split-string (substring string 0 i) sep)) | ||
| 189 | (let ((rfs (read-from-string string i))) | ||
| 190 | (cons (car rfs) | ||
| 191 | (cvs-string->strings (substring string (cdr rfs)) sep))))))) | ||
| 192 | |||
| 193 | |||
| 194 | (defun cvs-string-fill (str n &optional filling truncate) | ||
| 195 | "Add FILLING (defaults to the space char) to STR to reach size N. | ||
| 196 | If STR is longer than N, truncate if TRUNCATE is set, else don't do anything." | ||
| 197 | (let ((l (length str))) | ||
| 198 | (if (> l n) | ||
| 199 | (if truncate (substring str 0 n) str) | ||
| 200 | (concat str (make-string (- n l) (or filling ? )))))) | ||
| 201 | |||
| 202 | ;;;; | ||
| 203 | ;;;; file names | ||
| 204 | ;;;; | ||
| 205 | |||
| 206 | (defsubst cvs-expand-dir-name (d) | ||
| 207 | (file-name-as-directory (expand-file-name d))) | ||
| 208 | |||
| 209 | ;;;; | ||
| 210 | ;;;; (interactive <foo>) support function | ||
| 211 | ;;;; | ||
| 212 | |||
| 213 | (defstruct (cvs-qtypedesc | ||
| 214 | (:constructor nil) (:copier nil) | ||
| 215 | (:constructor cvs-qtypedesc-create | ||
| 216 | (str2obj obj2str &optional complete hist-sym require))) | ||
| 217 | str2obj | ||
| 218 | obj2str | ||
| 219 | hist-sym | ||
| 220 | complete | ||
| 221 | require) | ||
| 222 | |||
| 223 | |||
| 224 | (defconst cvs-qtypedesc-string1 (cvs-qtypedesc-create 'identity 'identity t)) | ||
| 225 | (defconst cvs-qtypedesc-string (cvs-qtypedesc-create 'identity 'identity)) | ||
| 226 | (defconst cvs-qtypedesc-strings | ||
| 227 | (cvs-qtypedesc-create 'cvs-string->strings 'cvs-strings->string nil)) | ||
| 228 | |||
| 229 | (defun cvs-query-read (default prompt qtypedesc &optional hist-sym) | ||
| 230 | (let* ((qtypedesc (or qtypedesc cvs-qtypedesc-strings)) | ||
| 231 | (hist-sym (or hist-sym (cvs-qtypedesc-hist-sym qtypedesc))) | ||
| 232 | (complete (cvs-qtypedesc-complete qtypedesc)) | ||
| 233 | (completions (and (functionp complete) (funcall complete))) | ||
| 234 | (initval (funcall (cvs-qtypedesc-obj2str qtypedesc) default))) | ||
| 235 | (funcall (cvs-qtypedesc-str2obj qtypedesc) | ||
| 236 | (cond | ||
| 237 | ((null complete) (read-string prompt initval hist-sym)) | ||
| 238 | ((functionp complete) | ||
| 239 | (completing-read prompt completions | ||
| 240 | nil (cvs-qtypedesc-require qtypedesc) | ||
| 241 | initval hist-sym)) | ||
| 242 | (t initval))))) | ||
| 243 | |||
| 244 | ;;;; | ||
| 245 | ;;;; Flags handling | ||
| 246 | ;;;; | ||
| 247 | |||
| 248 | (defstruct (cvs-flags | ||
| 249 | (:constructor nil) | ||
| 250 | (:constructor -cvs-flags-make | ||
| 251 | (desc defaults &optional qtypedesc hist-sym))) | ||
| 252 | defaults persist desc qtypedesc hist-sym) | ||
| 253 | |||
| 254 | (defmacro cvs-flags-define (sym defaults | ||
| 255 | &optional desc qtypedesc hist-sym docstring) | ||
| 256 | `(defconst ,sym | ||
| 257 | (let ((bound (boundp ',sym))) | ||
| 258 | (if (and bound (cvs-flags-p ,sym)) ,sym | ||
| 259 | (let ((defaults ,defaults)) | ||
| 260 | (-cvs-flags-make ,desc | ||
| 261 | (if bound (cons ,sym (cdr defaults)) defaults) | ||
| 262 | ,qtypedesc ,hist-sym)))) | ||
| 263 | ,docstring)) | ||
| 264 | |||
| 265 | (defun cvs-flags-query (sym &optional desc arg) | ||
| 266 | "Query flags based on SYM. | ||
| 267 | Optional argument DESC will be used for the prompt | ||
| 268 | If ARG (or a prefix argument) is nil, just use the 0th default. | ||
| 269 | If it is a non-negative integer, use the corresponding default. | ||
| 270 | If it is a negative integer query for a new value of the corresponding | ||
| 271 | default and return that new value. | ||
| 272 | If it is \\[universal-argument], just query and return a value without | ||
| 273 | altering the defaults. | ||
| 274 | If it is \\[universal-argument] \\[universal-argument], behave just | ||
| 275 | as if a negative zero was provided." | ||
| 276 | (let* ((flags (symbol-value sym)) | ||
| 277 | (desc (or desc (cvs-flags-desc flags))) | ||
| 278 | (qtypedesc (cvs-flags-qtypedesc flags)) | ||
| 279 | (hist-sym (cvs-flags-hist-sym flags)) | ||
| 280 | (arg (if (eq arg 'noquery) 0 (or arg current-prefix-arg 0))) | ||
| 281 | (numarg (prefix-numeric-value arg)) | ||
| 282 | (defaults (cvs-flags-defaults flags)) | ||
| 283 | (permstr (if (< numarg 0) (format " (%sth default)" (- numarg))))) | ||
| 284 | ;; special case for universal-argument | ||
| 285 | (when (consp arg) | ||
| 286 | (setq permstr (if (> numarg 4) " (permanent)" "")) | ||
| 287 | (setq numarg 0)) | ||
| 288 | |||
| 289 | ;; sanity check | ||
| 290 | (unless (< (abs numarg) (length defaults)) | ||
| 291 | (error "There is no %sth default." (abs numarg))) | ||
| 292 | |||
| 293 | (if permstr | ||
| 294 | (let* ((prompt (format "%s%s: " desc permstr)) | ||
| 295 | (fs (cvs-query-read (nth (- numarg) (cvs-flags-defaults flags)) | ||
| 296 | prompt qtypedesc hist-sym))) | ||
| 297 | (when (not (equal permstr "")) | ||
| 298 | (setf (nth (- numarg) (cvs-flags-defaults flags)) fs)) | ||
| 299 | fs) | ||
| 300 | (nth numarg defaults)))) | ||
| 301 | |||
| 302 | (defsubst cvs-flags-set (sym index value) | ||
| 303 | "Set SYM's INDEX'th setting to VALUE." | ||
| 304 | (setf (nth index (cvs-flags-defaults (symbol-value sym))) value)) | ||
| 305 | |||
| 306 | ;;;; | ||
| 307 | ;;;; Prefix keys | ||
| 308 | ;;;; | ||
| 309 | |||
| 310 | (defconst cvs-prefix-number 10) | ||
| 311 | |||
| 312 | (defsubst cvs-prefix-sym (sym) (intern (concat (symbol-name sym) "-cps"))) | ||
| 313 | |||
| 314 | (defmacro cvs-prefix-define (sym docstring desc defaults | ||
| 315 | &optional qtypedesc hist-sym) | ||
| 316 | (let ((cps (cvs-prefix-sym sym))) | ||
| 317 | `(progn | ||
| 318 | (defvar ,sym nil ,(cons (or docstring "") " | ||
| 319 | See `cvs-prefix-set' for further description of the behavior.")) | ||
| 320 | (defconst ,cps | ||
| 321 | (let ((defaults ,defaults)) | ||
| 322 | ;; sanity ensurance | ||
| 323 | (unless (>= (length defaults) cvs-prefix-number) | ||
| 324 | (setq defaults (append defaults | ||
| 325 | (make-list (1- cvs-prefix-number) | ||
| 326 | (first defaults))))) | ||
| 327 | (-cvs-flags-make ,desc defaults ,qtypedesc ,hist-sym)))))) | ||
| 328 | |||
| 329 | (defun cvs-prefix-make-local (sym) | ||
| 330 | (let ((cps (cvs-prefix-sym sym))) | ||
| 331 | (make-local-variable sym) | ||
| 332 | (set (make-local-variable cps) (copy-cvs-flags (symbol-value cps))))) | ||
| 333 | |||
| 334 | (defun cvs-prefix-set (sym arg) | ||
| 335 | ;; we could distinguish between numeric and non-numeric prefix args instead of | ||
| 336 | ;; relying on that magic `4'. | ||
| 337 | "Set the cvs-prefix contained in SYM. | ||
| 338 | If ARG is between 0 and 9, it selects the corresponding default. | ||
| 339 | If ARG is negative (or \\[universal-argument] which corresponds to negative 0), | ||
| 340 | it queries the user and sets the -ARG'th default. | ||
| 341 | If ARG is greater than 9 (or \\[universal-argument] \\[universal-argument]), | ||
| 342 | the (ARG mod 10)'th prefix is made persistent. | ||
| 343 | If ARG is NIL toggle the PREFIX's value between its 0th default and NIL | ||
| 344 | and reset the persistence." | ||
| 345 | (let* ((prefix (symbol-value (cvs-prefix-sym sym))) | ||
| 346 | (numarg (if (integerp arg) arg 0)) | ||
| 347 | (defs (cvs-flags-defaults prefix))) | ||
| 348 | |||
| 349 | ;; set persistence if requested | ||
| 350 | (when (> (prefix-numeric-value arg) 9) | ||
| 351 | (setf (cvs-flags-persist prefix) t) | ||
| 352 | (setq numarg (mod numarg 10))) | ||
| 353 | |||
| 354 | ;; set the value | ||
| 355 | (set sym | ||
| 356 | (cond | ||
| 357 | ((null arg) | ||
| 358 | (setf (cvs-flags-persist prefix) nil) | ||
| 359 | (unless (symbol-value sym) (first (cvs-flags-defaults prefix)))) | ||
| 360 | |||
| 361 | ((or (consp arg) (< numarg 0)) | ||
| 362 | (setf (nth (- numarg) (cvs-flags-defaults prefix)) | ||
| 363 | (cvs-query-read (nth (- numarg) (cvs-flags-defaults prefix)) | ||
| 364 | (format "%s: " (cvs-flags-desc prefix)) | ||
| 365 | (cvs-flags-qtypedesc prefix) | ||
| 366 | (cvs-flags-hist-sym prefix)))) | ||
| 367 | (t (nth numarg (cvs-flags-defaults prefix))))) | ||
| 368 | (force-mode-line-update))) | ||
| 369 | |||
| 370 | (defun cvs-prefix-get (sym &optional read-only) | ||
| 371 | "Return the current value of the prefix SYM. | ||
| 372 | and reset it unless READ-ONLY is non-nil." | ||
| 373 | (prog1 (symbol-value sym) | ||
| 374 | (unless (or read-only | ||
| 375 | (cvs-flags-persist (symbol-value (cvs-prefix-sym sym)))) | ||
| 376 | (set sym nil) | ||
| 377 | (force-mode-line-update)))) | ||
| 378 | |||
| 379 | (provide 'pcvs-util) | ||
| 380 | |||
| 381 | ;;; pcl-cvs-util.el ends here | ||
diff --git a/lisp/pcvs.el b/lisp/pcvs.el new file mode 100644 index 00000000000..320ec3c58ac --- /dev/null +++ b/lisp/pcvs.el | |||
| @@ -0,0 +1,2122 @@ | |||
| 1 | ;;; pcvs.el -- A Front-end to CVS. | ||
| 2 | |||
| 3 | ;; Copyright (C) 1991-2000 Free Software Foundation, Inc. | ||
| 4 | |||
| 5 | ;; Author: (The PCL-CVS Trust) pcl-cvs@cyclic.com | ||
| 6 | ;; (Per Cederqvist) ceder@lysator.liu.se | ||
| 7 | ;; (Greg A. Woods) woods@weird.com | ||
| 8 | ;; (Jim Blandy) jimb@cyclic.com | ||
| 9 | ;; (Karl Fogel) kfogel@floss.red-bean.com | ||
| 10 | ;; (Jim Kingdon) kingdon@cyclic.com | ||
| 11 | ;; (Stefan Monnier) monnier@cs.yale.edu | ||
| 12 | ;; (Greg Klanderman) greg@alphatech.com | ||
| 13 | ;; (Jari Aalto+mail.emacs) jari.aalto@poboxes.com | ||
| 14 | ;; Maintainer: (Stefan Monnier) monnier+lists/cvs/pcl@flint.cs.yale.edu | ||
| 15 | ;; Keywords: CVS, version control, release management | ||
| 16 | ;; Version: $Name: $ | ||
| 17 | ;; Revision: $Id: pcl-cvs.el,v 1.75 2000/03/05 21:32:21 monnier Exp $ | ||
| 18 | |||
| 19 | ;; This file is part of GNU Emacs. | ||
| 20 | |||
| 21 | ;; GNU Emacs is free software; you can redistribute it and/or modify | ||
| 22 | ;; it under the terms of the GNU General Public License as published by | ||
| 23 | ;; the Free Software Foundation; either version 2, or (at your option) | ||
| 24 | ;; any later version. | ||
| 25 | |||
| 26 | ;; GNU Emacs is distributed in the hope that it will be useful, | ||
| 27 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 28 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 29 | ;; GNU General Public License for more details. | ||
| 30 | |||
| 31 | ;; You should have received a copy of the GNU General Public License | ||
| 32 | ;; along with GNU Emacs; see the file COPYING. If not, write to the | ||
| 33 | ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, | ||
| 34 | ;; Boston, MA 02111-1307, USA. | ||
| 35 | |||
| 36 | ;;; Commentary: | ||
| 37 | |||
| 38 | ;;; Todo: | ||
| 39 | |||
| 40 | ;; * FIX THE DOCUMENTATION | ||
| 41 | ;; | ||
| 42 | ;; * Emacs-21 adaptation | ||
| 43 | ;; ** use the new arg of save-some-buffers | ||
| 44 | ;; ** add toolbar entries | ||
| 45 | ;; ** use `format' now that it keeps properties | ||
| 46 | ;; ** use propertize | ||
| 47 | ;; ** add compatibility with older name's variables. | ||
| 48 | ;; | ||
| 49 | ;; * New Features | ||
| 50 | ;; | ||
| 51 | ;; ** marking | ||
| 52 | ;; *** marking directories should jump to just after the dir. | ||
| 53 | ;; *** allow (un)marking directories at a time with the mouse. | ||
| 54 | ;; *** marking with the mouse should not move point. | ||
| 55 | ;; | ||
| 56 | ;; ** liveness indicator | ||
| 57 | ;; | ||
| 58 | ;; ** indicate in docstring if the cmd understands the `b' prefix(es). | ||
| 59 | ;; | ||
| 60 | ;; ** call smerge-mode when opening CONFLICT files. | ||
| 61 | ;; | ||
| 62 | ;; ** after-parse-hook (to eliminate *.elc from Emacs' CVS repository :-) | ||
| 63 | ;; | ||
| 64 | ;; ** have vc-checkin delegate to cvs-mode-commit when applicable | ||
| 65 | ;; | ||
| 66 | ;; ** higher-level CVS operations | ||
| 67 | ;; | ||
| 68 | ;; *** cvs-mode-rename | ||
| 69 | ;; *** cvs-mode-branch | ||
| 70 | ;; | ||
| 71 | ;; ** module-level commands | ||
| 72 | ;; | ||
| 73 | ;; *** add support for parsing 'modules' file ("cvs co -c") | ||
| 74 | ;; | ||
| 75 | ;; *** cvs-mode-rcs2log | ||
| 76 | ;; *** cvs-rdiff | ||
| 77 | ;; *** cvs-release | ||
| 78 | ;; *** cvs-import | ||
| 79 | ;; *** C-u M-x cvs-checkout should ask for a cvsroot | ||
| 80 | ;; | ||
| 81 | ;; *** cvs-mode-handle-new-vendor-version | ||
| 82 | ;; - checks out module, or alternately does update join | ||
| 83 | ;; - does "cvs -n tag LAST_VENDOR" to find old files into *cvs* | ||
| 84 | ;; | ||
| 85 | ;; *** cvs-export | ||
| 86 | ;; (with completion on tag names and hooks to | ||
| 87 | ;; help generate full releases) | ||
| 88 | ;; | ||
| 89 | ;; ** allow cvs-cmd-do to either clear the marks or not. | ||
| 90 | ;; | ||
| 91 | ;; ** allow more concurrency: if the output buffer is busy, pick a new one. | ||
| 92 | ;; | ||
| 93 | ;; ** configurable layout/format of *cvs*. | ||
| 94 | ;; | ||
| 95 | ;; ** display stickiness information. And current CVS/Tag as well. | ||
| 96 | ;; | ||
| 97 | ;; ** cvs-log-mode should know how to extract version info | ||
| 98 | ;; cvs-log-current-tag is a nop right now :-( | ||
| 99 | ;; | ||
| 100 | ;; ** write 'cvs-mode-admin' to do arbitrary 'cvs admin' commands | ||
| 101 | ;; | ||
| 102 | ;; ** cvs-mode-incorporate | ||
| 103 | ;; It would merge in the status from one ``*cvs*'' buffer into another. | ||
| 104 | ;; This would be used to populate such a buffer that had been created with | ||
| 105 | ;; a `cvs {update,status,checkout} -l'. | ||
| 106 | ;; | ||
| 107 | ;; ** cvs-mode-(i)diff-other-{file,buffer,cvs-buffer} | ||
| 108 | ;; | ||
| 109 | ;; ** offer the choice to kill the process when the user kills the cvs buffer. | ||
| 110 | ;; right now, it's killed without further ado. | ||
| 111 | ;; | ||
| 112 | ;; ** make `cvs-mode-ignore' allow manually entering a pattern. | ||
| 113 | ;; to which dir should it apply ? | ||
| 114 | ;; | ||
| 115 | ;; ** cvs-mode-ignore should try to remove duplicate entries. | ||
| 116 | ;; | ||
| 117 | ;; * Old misfeatures | ||
| 118 | ;; | ||
| 119 | ;; ** cvs-mode-<foo> commands tend to require saving too many buffers | ||
| 120 | ;; they should only require saving the files concerned by the command | ||
| 121 | ;; | ||
| 122 | ;; * Secondary issues | ||
| 123 | ;; | ||
| 124 | ;; ** maybe poll/check CVS/Entries files to react to external `cvs' commands ? | ||
| 125 | ;; | ||
| 126 | ;; ** some kind of `cvs annotate' support ? | ||
| 127 | ;; but vc-annotate can be used instead. | ||
| 128 | ;; | ||
| 129 | ;; * probably not worth the trouble | ||
| 130 | ;; | ||
| 131 | ;; ** dynamic `g' mapping | ||
| 132 | ;; Make 'g', and perhaps other commands, use either cvs-update or | ||
| 133 | ;; cvs-examine depending on the read-only status of the cvs buffer, for | ||
| 134 | ;; instance. | ||
| 135 | ;; | ||
| 136 | ;; ** add message-levels so that we can hide some levels of messages | ||
| 137 | |||
| 138 | ;;; Code: | ||
| 139 | |||
| 140 | (eval-when-compile (require 'cl)) | ||
| 141 | (require 'ewoc) ;Ewoc was once cookie | ||
| 142 | (require 'pcvs-defs) | ||
| 143 | (require 'pcvs-util) | ||
| 144 | (require 'pcvs-parse) | ||
| 145 | (require 'pcvs-info) | ||
| 146 | |||
| 147 | |||
| 148 | ;;;; | ||
| 149 | ;;;; global vars | ||
| 150 | ;;;; | ||
| 151 | |||
| 152 | (defvar cvs-cookies) ;;nil | ||
| 153 | ;;"Handle for the cookie structure that is displayed in the *cvs* buffer.") | ||
| 154 | ;;(make-variable-buffer-local 'cvs-cookies) | ||
| 155 | |||
| 156 | ;;;; | ||
| 157 | ;;;; Dynamically scoped variables | ||
| 158 | ;;;; | ||
| 159 | |||
| 160 | (defvar cvs-from-vc nil "Bound to t inside VC advice.") | ||
| 161 | |||
| 162 | ;;;; | ||
| 163 | ;;;; flags variables | ||
| 164 | ;;;; | ||
| 165 | |||
| 166 | (defun cvs-defaults (&rest defs) | ||
| 167 | (let ((defs (cvs-first defs cvs-shared-start))) | ||
| 168 | (append defs | ||
| 169 | (make-list (- cvs-shared-start (length defs)) (first defs)) | ||
| 170 | cvs-shared-flags))) | ||
| 171 | |||
| 172 | ;; For cvs flags, we need to add "-f" to override the cvsrc settings | ||
| 173 | ;; we also want to evict the annoying -q and -Q options that hide useful | ||
| 174 | ;; information from pcl-cvs. | ||
| 175 | (cvs-flags-define cvs-cvs-flags '(("-f"))) | ||
| 176 | |||
| 177 | (cvs-flags-define cvs-checkout-flags (cvs-defaults '("-P"))) | ||
| 178 | (cvs-flags-define cvs-status-flags (cvs-defaults '("-v") nil)) | ||
| 179 | (cvs-flags-define cvs-log-flags (cvs-defaults nil)) | ||
| 180 | (cvs-flags-define cvs-diff-flags (cvs-defaults '("-u" "-N") '("-c" "-N"))) | ||
| 181 | (cvs-flags-define cvs-tag-flags (cvs-defaults nil)) | ||
| 182 | (cvs-flags-define cvs-add-flags (cvs-defaults nil)) | ||
| 183 | (cvs-flags-define cvs-commit-flags (cvs-defaults nil)) | ||
| 184 | (cvs-flags-define cvs-remove-flags (cvs-defaults nil)) | ||
| 185 | ;;(cvs-flags-define cvs-undo-flags (cvs-defaults nil)) | ||
| 186 | (cvs-flags-define cvs-update-flags (cvs-defaults '("-d" "-P"))) | ||
| 187 | |||
| 188 | (defun cvs-reread-cvsrc () | ||
| 189 | "Reset the default arguments to those in the `cvs-cvsrc-file'." | ||
| 190 | (interactive) | ||
| 191 | (let ((cvsrc (cvs-file-to-string cvs-cvsrc-file))) | ||
| 192 | (when (stringp cvsrc) | ||
| 193 | ;; fetch the values | ||
| 194 | (dolist (cmd '("cvs" "checkout" "status" "log" "diff" "tag" | ||
| 195 | "add" "commit" "remove" "update")) | ||
| 196 | (let* ((sym (intern (concat "cvs-" cmd "-flags"))) | ||
| 197 | (val (when (string-match (concat "^" cmd "\\s-\\(.*\\)$") cvsrc) | ||
| 198 | (cvs-string->strings (match-string 1 cvsrc))))) | ||
| 199 | (cvs-flags-set sym 0 val))) | ||
| 200 | ;; ensure that cvs doesn't have -q or -Q | ||
| 201 | (cvs-flags-set 'cvs-cvs-flags 0 | ||
| 202 | (cons "-f" | ||
| 203 | (cdr (cvs-partition | ||
| 204 | (lambda (x) (member x '("-q" "-Q"))) | ||
| 205 | (cvs-flags-query 'cvs-cvs-flags | ||
| 206 | nil 'noquery)))))))) | ||
| 207 | |||
| 208 | ;; initialize to cvsrc's default values | ||
| 209 | (cvs-reread-cvsrc) | ||
| 210 | |||
| 211 | |||
| 212 | ;;;; | ||
| 213 | ;;;; Mouse bindings and mode motion | ||
| 214 | ;;;; | ||
| 215 | |||
| 216 | (defun cvs-menu (e) | ||
| 217 | "Popup the CVS menu." | ||
| 218 | (interactive "e") | ||
| 219 | (mouse-set-point e) | ||
| 220 | (x-popup-menu e cvs-menu-map)) | ||
| 221 | |||
| 222 | (defvar cvs-mode-line-process nil | ||
| 223 | "Mode-line control for displaying info on cvs process status.") | ||
| 224 | |||
| 225 | |||
| 226 | ;;;; | ||
| 227 | ;;;; Query-Type-Descriptor for Tags | ||
| 228 | ;;;; | ||
| 229 | |||
| 230 | (autoload 'cvs-status-get-tags "cvs-status") | ||
| 231 | (defun cvs-tags-list () | ||
| 232 | "Return a list of acceptable tags, ready for completions." | ||
| 233 | (assert (cvs-buffer-p)) | ||
| 234 | (let ((marked (cvs-get-marked))) | ||
| 235 | (list* '("BASE") '("HEAD") | ||
| 236 | (when marked | ||
| 237 | (with-temp-buffer | ||
| 238 | (call-process cvs-program | ||
| 239 | nil ;no input | ||
| 240 | t ;output to current-buffer | ||
| 241 | nil ;don't update display while running | ||
| 242 | "status" | ||
| 243 | "-v" | ||
| 244 | (cvs-fileinfo->full-path (car marked))) | ||
| 245 | (goto-char (point-min)) | ||
| 246 | (let ((tags (cvs-status-get-tags))) | ||
| 247 | (when (listp tags) tags))))))) | ||
| 248 | |||
| 249 | (defvar cvs-tag-history nil) | ||
| 250 | (defconst cvs-qtypedesc-tag | ||
| 251 | (cvs-qtypedesc-create 'identity 'identity 'cvs-tags-list 'cvs-tag-history)) | ||
| 252 | |||
| 253 | ;;;; | ||
| 254 | |||
| 255 | (defun cvs-mode! (&optional -cvs-mode!-fun -cvs-mode!-noerror) | ||
| 256 | "Switch to the *cvs* buffer. | ||
| 257 | If -CVS-MODE!-FUN is provided, it is executed *cvs* being the current buffer | ||
| 258 | and with its window selected. Else, the *cvs* buffer is simply selected. | ||
| 259 | If -CVS-MODE!-NOERROR is non-nil, then failure to find a *cvs* buffer does | ||
| 260 | not generate an error and the current buffer is kept selected. | ||
| 261 | -CVS-MODE!-FUN is called interactively if applicable and else with no argument." | ||
| 262 | (let* ((-cvs-mode!-buf (current-buffer)) | ||
| 263 | (cvsbuf (cond ((cvs-buffer-p) (current-buffer)) | ||
| 264 | ((and cvs-buffer (cvs-buffer-p cvs-buffer)) cvs-buffer) | ||
| 265 | (-cvs-mode!-noerror (current-buffer)) | ||
| 266 | (t (error "can't find the *cvs* buffer.")))) | ||
| 267 | (-cvs-mode!-wrapper cvs-minor-wrap-function) | ||
| 268 | (-cvs-mode!-cont (lambda () | ||
| 269 | (save-current-buffer | ||
| 270 | (if (commandp -cvs-mode!-fun) | ||
| 271 | (call-interactively -cvs-mode!-fun) | ||
| 272 | (funcall -cvs-mode!-fun)))))) | ||
| 273 | (if (not -cvs-mode!-fun) (set-buffer cvsbuf) | ||
| 274 | (let ((cvs-mode!-buf (current-buffer)) | ||
| 275 | (cvs-mode!-owin (selected-window)) | ||
| 276 | (cvs-mode!-nwin (get-buffer-window cvsbuf 'visible))) | ||
| 277 | (unwind-protect | ||
| 278 | (progn | ||
| 279 | (set-buffer cvsbuf) | ||
| 280 | (when cvs-mode!-nwin (select-window cvs-mode!-nwin)) | ||
| 281 | (if -cvs-mode!-wrapper | ||
| 282 | (funcall -cvs-mode!-wrapper -cvs-mode!-buf -cvs-mode!-cont) | ||
| 283 | (funcall -cvs-mode!-cont))) | ||
| 284 | (set-buffer cvs-mode!-buf) | ||
| 285 | (when (and cvs-mode!-nwin (eq cvs-mode!-nwin (selected-window))) | ||
| 286 | ;; the selected window has not been changed by FUN | ||
| 287 | (select-window cvs-mode!-owin))))))) | ||
| 288 | |||
| 289 | ;;;; | ||
| 290 | ;;;; Prefixes | ||
| 291 | ;;;; | ||
| 292 | |||
| 293 | (defvar cvs-branches (list cvs-vendor-branch "HEAD" "HEAD")) | ||
| 294 | (cvs-prefix-define cvs-branch-prefix | ||
| 295 | "Current selected branch." | ||
| 296 | "version" | ||
| 297 | (cons cvs-vendor-branch cvs-branches) | ||
| 298 | cvs-qtypedesc-tag) | ||
| 299 | |||
| 300 | (defun cvs-set-branch-prefix (arg) | ||
| 301 | "Set the branch prefix to take action at the next command. | ||
| 302 | See `cvs-prefix-set' for a further the description of the behavior. | ||
| 303 | \\[universal-argument] 1 selects the vendor branch | ||
| 304 | and \\[universal-argument] 2 selects the HEAD." | ||
| 305 | (interactive "P") | ||
| 306 | (cvs-mode!) | ||
| 307 | (cvs-prefix-set 'cvs-branch-prefix arg)) | ||
| 308 | |||
| 309 | (defun cvs-add-branch-prefix (flags &optional arg) | ||
| 310 | "Add branch selection argument if the branch prefix was set. | ||
| 311 | The argument is added (or not) to the list of FLAGS and is constructed | ||
| 312 | by appending the branch to ARG which defaults to \"-r\"." | ||
| 313 | (let ((branch (cvs-prefix-get 'cvs-branch-prefix))) | ||
| 314 | ;; deactivate the secondary prefix, even if not used. | ||
| 315 | (cvs-prefix-get 'cvs-secondary-branch-prefix) | ||
| 316 | (if branch (cons (concat (or arg "-r") branch) flags) flags))) | ||
| 317 | |||
| 318 | (cvs-prefix-define cvs-secondary-branch-prefix | ||
| 319 | "Current secondary selected branch." | ||
| 320 | "version" | ||
| 321 | (cons cvs-vendor-branch cvs-branches) | ||
| 322 | cvs-qtypedesc-tag) | ||
| 323 | |||
| 324 | (defun cvs-set-secondary-branch-prefix (arg) | ||
| 325 | "Set the branch prefix to take action at the next command. | ||
| 326 | See `cvs-prefix-set' for a further the description of the behavior. | ||
| 327 | \\[universal-argument] 1 selects the vendor branch | ||
| 328 | and \\[universal-argument] 2 selects the HEAD." | ||
| 329 | (interactive "P") | ||
| 330 | (cvs-mode!) | ||
| 331 | (cvs-prefix-set 'cvs-secondary-branch-prefix arg)) | ||
| 332 | |||
| 333 | (defun cvs-add-secondary-branch-prefix (flags &optional arg) | ||
| 334 | "Add branch selection argument if the secondary branch prefix was set. | ||
| 335 | The argument is added (or not) to the list of FLAGS and is constructed | ||
| 336 | by appending the branch to ARG which defaults to \"-r\". | ||
| 337 | Since the `cvs-secondary-branch-prefix' is only active if the primary | ||
| 338 | prefix is active, it is important to read the secondary prefix before | ||
| 339 | the primay since reading the primary can deactivate it." | ||
| 340 | (let ((branch (and (cvs-prefix-get 'cvs-branch-prefix 'read-only) | ||
| 341 | (cvs-prefix-get 'cvs-secondary-branch-prefix)))) | ||
| 342 | (if branch (cons (concat (or arg "-r") branch) flags) flags))) | ||
| 343 | |||
| 344 | ;;;; | ||
| 345 | |||
| 346 | (define-minor-mode | ||
| 347 | cvs-minor-mode | ||
| 348 | " | ||
| 349 | This mode is used for buffers related to a main *cvs* buffer. | ||
| 350 | All the `cvs-mode' buffer operations are simply rebound under | ||
| 351 | the \\[cvs-mode-map] prefix. | ||
| 352 | " | ||
| 353 | nil " CVS") | ||
| 354 | (put 'cvs-minor-mode 'permanent-local t) | ||
| 355 | |||
| 356 | |||
| 357 | (defvar cvs-temp-buffers nil) | ||
| 358 | (defun cvs-temp-buffer (&optional cmd normal nosetup) | ||
| 359 | "Create a temporary buffer to run CMD in. | ||
| 360 | If CMD is a string, use it to lookup `cvs-buffer-name-alist' to find | ||
| 361 | the buffer name to be used and its `major-mode'. | ||
| 362 | |||
| 363 | The selected window will not be changed. The new buffer will not maintain undo | ||
| 364 | information and will be read-only unless NORMAL is non-nil. It will be emptied | ||
| 365 | \(unless NOSETUP is non-nil\) and its `default-directory' will be inherited | ||
| 366 | from the current buffer." | ||
| 367 | (let* ((cvs-buf (current-buffer)) | ||
| 368 | (info (cdr (assoc cmd cvs-buffer-name-alist))) | ||
| 369 | (name (eval (first info))) | ||
| 370 | (mode (second info)) | ||
| 371 | (dir default-directory) | ||
| 372 | (buf (cond | ||
| 373 | (name (cvs-get-buffer-create name)) | ||
| 374 | ((and (bufferp cvs-temp-buffer) (buffer-name cvs-temp-buffer)) | ||
| 375 | cvs-temp-buffer) | ||
| 376 | (t | ||
| 377 | (set (make-local-variable 'cvs-temp-buffer) | ||
| 378 | (cvs-get-buffer-create | ||
| 379 | (eval cvs-temp-buffer-name) 'noreuse)))))) | ||
| 380 | |||
| 381 | ;; handle the potential pre-existing process | ||
| 382 | (let ((proc (get-buffer-process buf))) | ||
| 383 | (when (and (not normal) (processp proc) | ||
| 384 | (memq (process-status proc) '(run stop))) | ||
| 385 | (error "Can not run two cvs processes simultaneously"))) | ||
| 386 | |||
| 387 | (if (not name) (kill-local-variable 'other-window-scroll-buffer) | ||
| 388 | ;; Strangely, if no window is created, `display-buffer' ends up | ||
| 389 | ;; doing a `switch-to-buffer' which does a `set-buffer', hence | ||
| 390 | ;; the need for `save-excursion'. | ||
| 391 | (unless nosetup (save-excursion (display-buffer buf))) | ||
| 392 | ;; FIXME: this doesn't do the right thing if the user later on | ||
| 393 | ;; does a `find-file-other-window' and `scroll-other-window' | ||
| 394 | (set (make-local-variable 'other-window-scroll-buffer) buf)) | ||
| 395 | |||
| 396 | (add-to-list 'cvs-temp-buffers buf) | ||
| 397 | |||
| 398 | (with-current-buffer buf | ||
| 399 | (setq buffer-read-only nil) | ||
| 400 | (setq default-directory dir) | ||
| 401 | (unless nosetup (erase-buffer)) | ||
| 402 | (set (make-local-variable 'cvs-buffer) cvs-buf) | ||
| 403 | ;;(cvs-minor-mode 1) | ||
| 404 | (let ((lbd list-buffers-directory)) | ||
| 405 | (if (fboundp mode) (funcall mode) (fundamental-mode)) | ||
| 406 | (when lbd (set (make-local-variable 'list-buffers-directory) lbd))) | ||
| 407 | (cvs-minor-mode 1) | ||
| 408 | ;;(set (make-local-variable 'cvs-buffer) cvs-buf) | ||
| 409 | (unless normal | ||
| 410 | (setq buffer-read-only t) | ||
| 411 | (buffer-disable-undo)) | ||
| 412 | buf))) | ||
| 413 | |||
| 414 | (defun cvs-mode-kill-buffers () | ||
| 415 | "Kill all the \"temporary\" buffers created by the *cvs* buffer." | ||
| 416 | (interactive) | ||
| 417 | (dolist (buf cvs-temp-buffers) (ignore-errors (kill-buffer buf)))) | ||
| 418 | |||
| 419 | (defun cvs-make-cvs-buffer (dir &optional new) | ||
| 420 | "Create the *cvs* buffer for directory DIR. | ||
| 421 | If non-nil, NEW means to create a new buffer no matter what." | ||
| 422 | ;; the real cvs-buffer creation | ||
| 423 | (setq dir (cvs-expand-dir-name dir)) | ||
| 424 | (let* ((buffer-name (eval cvs-buffer-name)) | ||
| 425 | (buffer | ||
| 426 | (or (and (not new) | ||
| 427 | (eq cvs-reuse-cvs-buffer 'current) | ||
| 428 | (cvs-buffer-p) ;reuse the current buffer if possible | ||
| 429 | (current-buffer)) | ||
| 430 | ;; look for another cvs buffer visiting the same directory | ||
| 431 | (save-excursion | ||
| 432 | (unless new | ||
| 433 | (dolist (buffer (cons (current-buffer) (buffer-list))) | ||
| 434 | (set-buffer buffer) | ||
| 435 | (and (cvs-buffer-p) | ||
| 436 | (case cvs-reuse-cvs-buffer | ||
| 437 | (always t) | ||
| 438 | (subdir | ||
| 439 | (or (cvs-string-prefix-p default-directory dir) | ||
| 440 | (cvs-string-prefix-p dir default-directory))) | ||
| 441 | (samedir (string= default-directory dir))) | ||
| 442 | (return buffer))))) | ||
| 443 | ;; we really have to create a new buffer: | ||
| 444 | ;; we temporarily bind cwd to "" to prevent | ||
| 445 | ;; create-file-buffer from using directory info | ||
| 446 | ;; unless it is explicitly in the cvs-buffer-name. | ||
| 447 | (cvs-get-buffer-create buffer-name new)))) | ||
| 448 | (with-current-buffer buffer | ||
| 449 | (or | ||
| 450 | (and (string= dir default-directory) (cvs-buffer-p) | ||
| 451 | ;; just a refresh | ||
| 452 | (ignore-errors | ||
| 453 | (cvs-cleanup-collection cvs-cookies nil nil t) | ||
| 454 | (current-buffer))) | ||
| 455 | ;; setup from scratch | ||
| 456 | (progn | ||
| 457 | (setq default-directory dir) | ||
| 458 | (setq buffer-read-only nil) | ||
| 459 | (erase-buffer) | ||
| 460 | (setq buffer-read-only t) | ||
| 461 | (cvs-mode) | ||
| 462 | (set (make-local-variable 'list-buffers-directory) buffer-name) | ||
| 463 | ;;(set (make-local-variable 'cvs-temp-buffer) (cvs-temp-buffer)) | ||
| 464 | (let ((cookies | ||
| 465 | (ewoc-create | ||
| 466 | buffer 'cvs-fileinfo-pp | ||
| 467 | (format "%s\n\nRepository : %s\nWorking directory: %s\n" | ||
| 468 | cvs-startup-message | ||
| 469 | (directory-file-name (cvs-get-cvsroot)) | ||
| 470 | dir)))) | ||
| 471 | (set (make-local-variable 'cvs-cookies) cookies) | ||
| 472 | (ewoc-enter-first | ||
| 473 | cookies | ||
| 474 | (cvs-create-fileinfo 'MESSAGE "" " " "\n" :subtype 'HEADER)) | ||
| 475 | (ewoc-enter-last | ||
| 476 | cookies | ||
| 477 | (cvs-create-fileinfo 'MESSAGE "" " " "\n" :subtype 'FOOTER)) | ||
| 478 | (make-local-hook 'kill-buffer-hook) | ||
| 479 | (add-hook 'kill-buffer-hook | ||
| 480 | (lambda () | ||
| 481 | (ignore-errors (kill-buffer cvs-temp-buffer))) | ||
| 482 | nil t) | ||
| 483 | ;;(set-buffer buf) | ||
| 484 | buffer)))))) | ||
| 485 | |||
| 486 | (defun* cvs-cmd-do (cmd dir flags fis new | ||
| 487 | &key cvsargs noexist dont-change-disc noshow) | ||
| 488 | (let* ((dir (file-name-as-directory | ||
| 489 | (abbreviate-file-name (expand-file-name dir)))) | ||
| 490 | (cvsbuf (cvs-make-cvs-buffer dir new))) | ||
| 491 | ;; Check that dir is under CVS control. | ||
| 492 | (unless (file-directory-p dir) | ||
| 493 | (error "%s is not a directory." dir)) | ||
| 494 | (unless (or noexist (file-directory-p (expand-file-name "CVS" dir))) | ||
| 495 | (error "%s does not contain CVS controlled files." dir)) | ||
| 496 | |||
| 497 | (set-buffer cvsbuf) | ||
| 498 | (cvs-mode-run cmd flags fis | ||
| 499 | :cvsargs cvsargs :dont-change-disc dont-change-disc) | ||
| 500 | |||
| 501 | (if noshow cvsbuf | ||
| 502 | (let ((pop-up-windows nil)) (pop-to-buffer cvsbuf))))) | ||
| 503 | ;; (funcall (if (and (boundp 'pop-up-frames) pop-up-frames) | ||
| 504 | ;; 'pop-to-buffer 'switch-to-buffer) | ||
| 505 | ;; cvsbuf)))) | ||
| 506 | |||
| 507 | ;;---------- | ||
| 508 | (defun cvs-run-process (args fis postprocess &optional single-dir) | ||
| 509 | (assert (cvs-buffer-p cvs-buffer)) | ||
| 510 | (save-current-buffer | ||
| 511 | (let ((procbuf (current-buffer)) | ||
| 512 | (cvsbuf cvs-buffer) | ||
| 513 | (single-dir (or single-dir (eq cvs-execute-single-dir t)))) | ||
| 514 | |||
| 515 | (set-buffer procbuf) | ||
| 516 | (goto-char (point-max)) | ||
| 517 | (unless (bolp) (let ((inhibit-read-only t)) (insert "\n"))) | ||
| 518 | ;; find the set of files we'll process in this round | ||
| 519 | (let* ((dir+files+rest | ||
| 520 | (if (or (null fis) (not single-dir)) | ||
| 521 | ;; not single-dir mode: just process the whole thing | ||
| 522 | (list "" (mapcar 'cvs-fileinfo->full-path fis) nil) | ||
| 523 | ;; single-dir mode: extract the same-dir-elements | ||
| 524 | (let ((dir (cvs-fileinfo->dir (car fis)))) | ||
| 525 | ;; output the concerned dir so the parser can translate paths | ||
| 526 | (let ((inhibit-read-only t)) | ||
| 527 | (insert "pcl-cvs: descending directory " dir "\n")) | ||
| 528 | ;; loop to find the same-dir-elems | ||
| 529 | (do* ((files () (cons (cvs-fileinfo->file fi) files)) | ||
| 530 | (fis fis (cdr fis)) | ||
| 531 | (fi (car fis) (car fis))) | ||
| 532 | ((not (and fis (string= dir (cvs-fileinfo->dir fi)))) | ||
| 533 | (list dir files fis)))))) | ||
| 534 | (dir (first dir+files+rest)) | ||
| 535 | (files (second dir+files+rest)) | ||
| 536 | (rest (third dir+files+rest))) | ||
| 537 | |||
| 538 | ;; setup the (current) process buffer | ||
| 539 | (set (make-local-variable 'cvs-postprocess) | ||
| 540 | (if (null rest) | ||
| 541 | ;; this is the last invocation | ||
| 542 | postprocess | ||
| 543 | ;; else, we have to register ourselves to be rerun on the rest | ||
| 544 | `(cvs-run-process ',args ',rest ',postprocess ',single-dir))) | ||
| 545 | (make-local-hook 'kill-buffer-hook) | ||
| 546 | (add-hook 'kill-buffer-hook | ||
| 547 | (lambda () | ||
| 548 | (let ((proc (get-buffer-process (current-buffer)))) | ||
| 549 | (when (processp proc) | ||
| 550 | (set-process-filter proc nil) | ||
| 551 | (set-process-sentinel proc nil) | ||
| 552 | (delete-process proc)))) | ||
| 553 | nil t) | ||
| 554 | |||
| 555 | ;; create the new process and setup the procbuffer correspondingly | ||
| 556 | (let* ((args (append (cvs-flags-query 'cvs-cvs-flags nil 'noquery) | ||
| 557 | (if cvs-cvsroot (list "-d" cvs-cvsroot)) | ||
| 558 | args | ||
| 559 | files)) | ||
| 560 | (process-connection-type nil) ; Use a pipe, not a pty. | ||
| 561 | (process | ||
| 562 | ;; the process will be run in the selected dir | ||
| 563 | (let ((default-directory (cvs-expand-dir-name dir))) | ||
| 564 | (apply 'start-process "cvs" procbuf cvs-program args)))) | ||
| 565 | (set-process-sentinel process 'cvs-sentinel) | ||
| 566 | (set-process-filter process 'cvs-update-filter) | ||
| 567 | (set-marker (process-mark process) (point-max)) | ||
| 568 | (ignore-errors (process-send-eof process)) ;close its stdin to avoid hangs | ||
| 569 | |||
| 570 | ;; now finish setting up the cvs-buffer | ||
| 571 | (set-buffer cvsbuf) | ||
| 572 | (setq cvs-mode-line-process (symbol-name (process-status process))) | ||
| 573 | (force-mode-line-update))))) | ||
| 574 | |||
| 575 | ;; The following line is said to improve display updates on some | ||
| 576 | ;; emacsen. It shouldn't be needed, but it does no harm. | ||
| 577 | (sit-for 0)) | ||
| 578 | |||
| 579 | (defun cvs-update-header (args fis) ; inline | ||
| 580 | (let* ((lastarg nil) | ||
| 581 | ;; filter out the largish commit message | ||
| 582 | (args (mapcar (lambda (arg) | ||
| 583 | (cond | ||
| 584 | ((and (eq lastarg nil) (string= arg "commit")) | ||
| 585 | (setq lastarg 'commit) arg) | ||
| 586 | ((and (eq lastarg 'commit) (string= arg "-m")) | ||
| 587 | (setq lastarg '-m) arg) | ||
| 588 | ((eq lastarg '-m) | ||
| 589 | (setq lastarg 'done) "<log message>") | ||
| 590 | (t arg))) | ||
| 591 | args)) | ||
| 592 | ;; turn them into a string | ||
| 593 | (arg (cvs-strings->string | ||
| 594 | (append (cvs-flags-query 'cvs-cvs-flags nil 'noquery) | ||
| 595 | (if cvs-cvsroot (list "-d" cvs-cvsroot)) | ||
| 596 | args | ||
| 597 | (mapcar 'cvs-fileinfo->full-path fis)))) | ||
| 598 | (str (if args (concat "-- Running " cvs-program " " arg " ...\n") | ||
| 599 | "\n"))) | ||
| 600 | (if nil (insert str) ;inline | ||
| 601 | ;;(with-current-buffer cvs-buffer | ||
| 602 | (let* ((tin0 (ewoc-nth cvs-cookies 0)) | ||
| 603 | (tin-1 (ewoc-nth cvs-cookies -1)) | ||
| 604 | (header (ewoc-data tin0)) | ||
| 605 | (footer (ewoc-data tin-1)) | ||
| 606 | (prev-msg (cvs-fileinfo->full-log header)) | ||
| 607 | (tin tin0)) | ||
| 608 | (assert (and (eq 'HEADER (cvs-fileinfo->subtype header)) | ||
| 609 | (eq 'FOOTER (cvs-fileinfo->subtype footer)))) | ||
| 610 | ;; look for the first *real* fileinfo (to determine emptyness) | ||
| 611 | (while | ||
| 612 | (and tin | ||
| 613 | (memq (cvs-fileinfo->type (ewoc-data tin)) | ||
| 614 | '(MESSAGE DIRCHANGE))) | ||
| 615 | (setq tin (ewoc-next cvs-cookies tin))) | ||
| 616 | ;; cleanup the prev-msg | ||
| 617 | (when (string-match "Running \\(.*\\) ...\n" prev-msg) | ||
| 618 | (setq prev-msg | ||
| 619 | (concat | ||
| 620 | "-- last cmd: " | ||
| 621 | (match-string 1 prev-msg) | ||
| 622 | " --"))) | ||
| 623 | ;; set the new header and footer | ||
| 624 | (setf (cvs-fileinfo->full-log header) str) | ||
| 625 | (setf (cvs-fileinfo->full-log footer) | ||
| 626 | (concat "\n--------------------- " | ||
| 627 | (if tin "End" "Empty") | ||
| 628 | " ---------------------\n" | ||
| 629 | prev-msg)) | ||
| 630 | (ewoc-invalidate cvs-cookies tin0 tin-1)))));;) | ||
| 631 | |||
| 632 | |||
| 633 | ;;---------- | ||
| 634 | (defun cvs-sentinel (proc msg) | ||
| 635 | "Sentinel for the cvs update process. | ||
| 636 | This is responsible for parsing the output from the cvs update when | ||
| 637 | it is finished." | ||
| 638 | (when (memq (process-status proc) '(signal exit)) | ||
| 639 | (if (null (buffer-name (process-buffer proc))) | ||
| 640 | ;;(set-process-buffer proc nil) | ||
| 641 | (error "cvs' process buffer was killed") | ||
| 642 | (let* ((obuf (current-buffer)) | ||
| 643 | (procbuffer (process-buffer proc))) | ||
| 644 | (set-buffer (with-current-buffer procbuffer cvs-buffer)) | ||
| 645 | (setq cvs-mode-line-process (symbol-name (process-status proc))) | ||
| 646 | (force-mode-line-update) | ||
| 647 | (set-buffer procbuffer) | ||
| 648 | (let ((cvs-postproc cvs-postprocess)) | ||
| 649 | ;; Since the buffer and mode line will show that the | ||
| 650 | ;; process is dead, we can delete it now. Otherwise it | ||
| 651 | ;; will stay around until M-x list-processes. | ||
| 652 | (delete-process proc) | ||
| 653 | (setq cvs-postprocess nil) | ||
| 654 | ;; do the postprocessing like parsing and such | ||
| 655 | (save-excursion (eval cvs-postproc)) | ||
| 656 | ;; check whether something is left | ||
| 657 | (unless cvs-postprocess | ||
| 658 | (buffer-enable-undo) | ||
| 659 | (with-current-buffer cvs-buffer | ||
| 660 | (cvs-update-header nil nil) ;FIXME: might need to be inline | ||
| 661 | (message "CVS process has completed")))) | ||
| 662 | ;; This might not even be necessary | ||
| 663 | (set-buffer obuf))))) | ||
| 664 | |||
| 665 | ;;---------- | ||
| 666 | (defun cvs-parse-process (dcd &optional subdir) | ||
| 667 | "FIXME: bad name, no doc" | ||
| 668 | (let* ((from-buf (current-buffer)) | ||
| 669 | (fileinfos (cvs-parse-buffer 'cvs-parse-table dcd subdir)) | ||
| 670 | (_ (set-buffer cvs-buffer)) | ||
| 671 | last | ||
| 672 | (from-pt (point))) | ||
| 673 | ;; add the new fileinfos | ||
| 674 | (dolist (fi fileinfos) | ||
| 675 | (setq last (cvs-addto-collection cvs-cookies fi last))) | ||
| 676 | (cvs-cleanup-collection cvs-cookies | ||
| 677 | (eq cvs-auto-remove-handled t) | ||
| 678 | cvs-auto-remove-directories | ||
| 679 | nil) | ||
| 680 | ;; update the display (might be unnecessary) | ||
| 681 | (ewoc-refresh cvs-cookies) | ||
| 682 | ;; revert buffers if necessary | ||
| 683 | (when (and cvs-auto-revert (not dcd) (not cvs-from-vc)) | ||
| 684 | (cvs-revert-if-needed fileinfos)) | ||
| 685 | ;; get back to where we were. `save-excursion' doesn't seem to | ||
| 686 | ;; work in this case, probably because the buffer is reconstructed | ||
| 687 | ;; by the cookie code. | ||
| 688 | (goto-char from-pt) | ||
| 689 | (set-buffer from-buf))) | ||
| 690 | |||
| 691 | (defmacro defun-cvs-mode (fun args docstring interact &rest body) | ||
| 692 | "Define a function to be used in a *cvs* buffer. | ||
| 693 | This will look for a *cvs* buffer and execute BODY in it. | ||
| 694 | Since the interactive arguments might need to be queried after | ||
| 695 | switching to the *cvs* buffer, the generic code is rather ugly, | ||
| 696 | but luckily we can often use simpler alternatives. | ||
| 697 | |||
| 698 | FUN can be either a symbol (i.e. STYLE is nil) or a cons (FUN . STYLE). | ||
| 699 | ARGS and DOCSTRING are the normal argument list. | ||
| 700 | INTERACT is the interactive specification or nil for non-commands. | ||
| 701 | |||
| 702 | STYLE can be either SIMPLE, NOARGS or DOUBLE. It's an error for it | ||
| 703 | to have any other value, unless other details of the function make it | ||
| 704 | clear what alternative to use. | ||
| 705 | - SIMPLE will get all the interactive arguments from the original buffer. | ||
| 706 | - NOARGS will get all the arguments from the *cvs* buffer and will | ||
| 707 | always behave as if called interactively. | ||
| 708 | - DOUBLE is the generic case." | ||
| 709 | (let ((style (cvs-cdr fun)) | ||
| 710 | (fun (cvs-car fun))) | ||
| 711 | (cond | ||
| 712 | ;; a trivial interaction, no need to move it | ||
| 713 | ((or (eq style 'SIMPLE) | ||
| 714 | (null (second interact)) | ||
| 715 | (stringp (second interact))) | ||
| 716 | `(defun ,fun ,args ,docstring ,interact | ||
| 717 | (cvs-mode! (lambda () ,@body)))) | ||
| 718 | |||
| 719 | ;; fun is only called interactively: move all the args to the inner fun | ||
| 720 | ((eq style 'NOARGS) | ||
| 721 | `(defun ,fun () ,docstring (interactive) | ||
| 722 | (cvs-mode! (lambda ,args ,interact ,@body)))) | ||
| 723 | |||
| 724 | ;; bad case | ||
| 725 | ((eq style 'DOUBLE) | ||
| 726 | (string-match ".*" docstring) | ||
| 727 | (let ((line1 (match-string 0 docstring)) | ||
| 728 | (restdoc (substring docstring (match-end 0))) | ||
| 729 | (fun-1 (intern (concat (symbol-name fun) "-1")))) | ||
| 730 | `(progn | ||
| 731 | (defun ,fun-1 ,args | ||
| 732 | ,(concat docstring "\nThis function only works within a *cvs* buffer. | ||
| 733 | For interactive use, use `" (symbol-name fun) "' instead.") | ||
| 734 | ,interact | ||
| 735 | ,@body) | ||
| 736 | (defun ,fun () | ||
| 737 | ,(concat line1 "\nWrapper function that switches to a *cvs* buffer | ||
| 738 | before calling the real function `" (symbol-name fun-1) "'.\n") | ||
| 739 | (interactive) | ||
| 740 | (cvs-mode! ',fun-1))))) | ||
| 741 | |||
| 742 | (t (error "unknown style %s in `defun-cvs-mode'" style))))) | ||
| 743 | (def-edebug-spec defun-cvs-mode (&define sexp lambda-list stringp ("interactive" interactive) def-body)) | ||
| 744 | |||
| 745 | (defun-cvs-mode cvs-mode-kill-process () | ||
| 746 | "Kill the temporary buffer and associated process." | ||
| 747 | (interactive) | ||
| 748 | (when (and (bufferp cvs-temp-buffer) (buffer-name cvs-temp-buffer)) | ||
| 749 | (let ((proc (get-buffer-process cvs-temp-buffer))) | ||
| 750 | (when proc (delete-process proc))))) | ||
| 751 | |||
| 752 | ;;; | ||
| 753 | ;;; Maintaining the collection in the face of updates | ||
| 754 | ;;; | ||
| 755 | |||
| 756 | (defun cvs-addto-collection (c fi &optional tin) | ||
| 757 | "Add FI to C and return a tin. | ||
| 758 | FI is inserted in its proper place or maybe even merged with a preexisting | ||
| 759 | fileinfo if applicable. | ||
| 760 | TIN specifies an optional starting point." | ||
| 761 | (unless tin (setq tin (ewoc-nth c 0))) | ||
| 762 | (while (and tin (cvs-fileinfo< fi (ewoc-data tin))) | ||
| 763 | (setq tin (ewoc-prev c tin))) | ||
| 764 | (if (null tin) (progn (ewoc-enter-first c fi) nil) ;empty collection | ||
| 765 | (assert (not (cvs-fileinfo< fi (ewoc-data tin)))) | ||
| 766 | (let ((next-tin (ewoc-next c tin))) | ||
| 767 | (while (not (or (null next-tin) | ||
| 768 | (cvs-fileinfo< fi (ewoc-data next-tin)))) | ||
| 769 | (setq tin next-tin next-tin (ewoc-next c next-tin))) | ||
| 770 | (if (cvs-fileinfo< (ewoc-data tin) fi) | ||
| 771 | ;; tin < fi < next-tin | ||
| 772 | (ewoc-enter-after c tin fi) | ||
| 773 | ;; fi == tin | ||
| 774 | (cvs-fileinfo-update (ewoc-data tin) fi) | ||
| 775 | (ewoc-invalidate c tin)) | ||
| 776 | tin))) | ||
| 777 | |||
| 778 | ;; called at the following times: | ||
| 779 | ;; - postparse ((eq cvs-auto-remove-handled t) cvs-auto-remove-directories nil) | ||
| 780 | ;; - pre-run ((eq cvs-auto-remove-handled 'delayed) nil t) | ||
| 781 | ;; - remove-handled (t (or cvs-auto-remove-directories 'handled) t) | ||
| 782 | ;; - cvs-cmd-do (nil nil t) | ||
| 783 | ;; - post-ignore (nil nil nil) | ||
| 784 | ;; - acknowledge (nil nil nil) | ||
| 785 | ;; - remove (nil nil nil) | ||
| 786 | (defun cvs-cleanup-collection (c rm-handled rm-dirs rm-msgs) | ||
| 787 | "Remove undesired entries. | ||
| 788 | C is the collection | ||
| 789 | RM-HANDLED if non-nil means remove handled entries. | ||
| 790 | RM-DIRS behaves like `cvs-auto-remove-directories'. | ||
| 791 | RM-MSGS if non-nil means remove messages." | ||
| 792 | (let (last-fi first-dir (rerun t)) | ||
| 793 | (while rerun | ||
| 794 | (setq rerun nil) | ||
| 795 | (setq first-dir t) | ||
| 796 | (setq last-fi (cvs-create-fileinfo 'DEAD "../" "" "")) ;place-holder | ||
| 797 | (ewoc-filter | ||
| 798 | c (lambda (fi) | ||
| 799 | (let* ((type (cvs-fileinfo->type fi)) | ||
| 800 | (subtype (cvs-fileinfo->subtype fi)) | ||
| 801 | (keep | ||
| 802 | (case type | ||
| 803 | ;; remove temp messages and keep the others | ||
| 804 | (MESSAGE | ||
| 805 | (or (memq subtype '(HEADER FOOTER)) | ||
| 806 | (not (or rm-msgs (eq subtype 'TEMP))))) | ||
| 807 | ;; remove entries | ||
| 808 | (DEAD nil) | ||
| 809 | ;; handled also? | ||
| 810 | (UP-TO-DATE (not rm-handled)) | ||
| 811 | ;; keep the rest | ||
| 812 | (t t)))) | ||
| 813 | |||
| 814 | ;; mark dirs for removal | ||
| 815 | (when (and keep rm-dirs | ||
| 816 | (eq (cvs-fileinfo->type last-fi) 'DIRCHANGE) | ||
| 817 | (not (when first-dir (setq first-dir nil) t)) | ||
| 818 | (or (eq rm-dirs 'all) | ||
| 819 | (not (cvs-string-prefix-p | ||
| 820 | (cvs-fileinfo->dir last-fi) | ||
| 821 | (cvs-fileinfo->dir fi))) | ||
| 822 | (and (eq type 'DIRCHANGE) (eq rm-dirs 'empty)) | ||
| 823 | (eq subtype 'FOOTER))) | ||
| 824 | (setf (cvs-fileinfo->type last-fi) 'DEAD) | ||
| 825 | (setq rerun t)) | ||
| 826 | (when keep (setq last-fi fi)))))))) | ||
| 827 | |||
| 828 | (defun cvs-get-cvsroot () | ||
| 829 | "Gets the CVSROOT for DIR." | ||
| 830 | (let ((cvs-cvsroot-file (expand-file-name "Root" "CVS"))) | ||
| 831 | (or (cvs-file-to-string cvs-cvsroot-file t) | ||
| 832 | cvs-cvsroot | ||
| 833 | (getenv "CVSROOT") | ||
| 834 | "?????"))) | ||
| 835 | |||
| 836 | (defun cvs-get-module () | ||
| 837 | "Return the current CVS module. | ||
| 838 | This usually doesn't really work but is a handy initval in a prompt." | ||
| 839 | (let* ((repfile (expand-file-name "Repository" "CVS")) | ||
| 840 | (rep (cvs-file-to-string repfile t))) | ||
| 841 | (cond | ||
| 842 | ((null rep) "") | ||
| 843 | ((not (file-name-absolute-p rep)) rep) | ||
| 844 | (t | ||
| 845 | (let* ((root (cvs-get-cvsroot)) | ||
| 846 | (str (concat (file-name-as-directory (or root "/")) " || " rep))) | ||
| 847 | (if (and root (string-match "\\(.*\\) || \\1\\(.*\\)\\'" str)) | ||
| 848 | (match-string 2 str) | ||
| 849 | (file-name-nondirectory rep))))))) | ||
| 850 | |||
| 851 | |||
| 852 | |||
| 853 | ;;;; | ||
| 854 | ;;;; running a "cvs checkout". | ||
| 855 | ;;;; | ||
| 856 | |||
| 857 | ;;;###autoload | ||
| 858 | (defun cvs-checkout (modules dir flags) | ||
| 859 | "Run a 'cvs checkout MODULES' in DIR. | ||
| 860 | Feed the output to a *cvs* buffer, display it in the current window, | ||
| 861 | and run `cvs-mode' on it. | ||
| 862 | |||
| 863 | With a prefix argument, prompt for cvs FLAGS to use." | ||
| 864 | (interactive | ||
| 865 | (list (cvs-string->strings (read-string "Module(s): " (cvs-get-module))) | ||
| 866 | (read-file-name "CVS Checkout Directory: " | ||
| 867 | nil default-directory nil) | ||
| 868 | (cvs-add-branch-prefix | ||
| 869 | (cvs-flags-query 'cvs-checkout-flags "cvs checkout flags")))) | ||
| 870 | (when (eq flags t) | ||
| 871 | (setf flags (cvs-flags-query 'cvs-checkout-flags nil 'noquery))) | ||
| 872 | (cvs-cmd-do "checkout" (or dir default-directory) | ||
| 873 | (append flags modules) nil 'new | ||
| 874 | :noexist t)) | ||
| 875 | |||
| 876 | |||
| 877 | ;;;; | ||
| 878 | ;;;; The code for running a "cvs update" and friends in various ways. | ||
| 879 | ;;;; | ||
| 880 | |||
| 881 | (defun-cvs-mode (cvs-mode-revert-buffer . SIMPLE) | ||
| 882 | (&optional ignore-auto noconfirm) | ||
| 883 | "Rerun cvs-examine on the current directory with the defauls flags." | ||
| 884 | (interactive) | ||
| 885 | (cvs-examine default-directory t)) | ||
| 886 | |||
| 887 | (defun cvs-query-directory (msg) | ||
| 888 | ;; last-command-char = ?\r hints that the command was run via M-x | ||
| 889 | (if (and (cvs-buffer-p) | ||
| 890 | (not current-prefix-arg) | ||
| 891 | (not (eq last-command-char ?\r))) | ||
| 892 | default-directory | ||
| 893 | (read-file-name msg nil default-directory nil))) | ||
| 894 | |||
| 895 | |||
| 896 | ;;;###autoload | ||
| 897 | (defun cvs-examine (directory flags &optional noshow) | ||
| 898 | "Run a `cvs -n update' in the specified DIRECTORY. | ||
| 899 | That is, check what needs to be done, but don't change the disc. | ||
| 900 | Feed the output to a *cvs* buffer and run `cvs-mode' on it. | ||
| 901 | With a prefix argument, prompt for a directory and cvs FLAGS to use. | ||
| 902 | A prefix arg >8 (ex: \\[universal-argument] \\[universal-argument]), | ||
| 903 | prevents reuse of an existing *cvs* buffer. | ||
| 904 | Optional argument NOSHOW if non-nil means not to display the buffer." | ||
| 905 | (interactive (list (cvs-query-directory "CVS Examine (directory): ") | ||
| 906 | (cvs-flags-query 'cvs-update-flags "cvs -n update flags"))) | ||
| 907 | (when (eq flags t) | ||
| 908 | (setf flags (cvs-flags-query 'cvs-update-flags nil 'noquery))) | ||
| 909 | (cvs-cmd-do "update" directory flags nil | ||
| 910 | (> (prefix-numeric-value current-prefix-arg) 8) | ||
| 911 | :cvsargs '("-n") | ||
| 912 | :noshow noshow | ||
| 913 | :dont-change-disc t)) | ||
| 914 | |||
| 915 | |||
| 916 | ;;;###autoload | ||
| 917 | (defun cvs-update (directory flags) | ||
| 918 | "Run a `cvs update' in the current working DIRECTORY. | ||
| 919 | Feed the output to a *cvs* buffer and run `cvs-mode' on it. | ||
| 920 | With a prefix argument, prompt for a directory and cvs FLAGS to use. | ||
| 921 | A prefix arg >8 (ex: \\[universal-argument] \\[universal-argument]), | ||
| 922 | prevents reuse of an existing *cvs* buffer." | ||
| 923 | (interactive (list (cvs-query-directory "CVS Update (directory): ") | ||
| 924 | (cvs-flags-query 'cvs-update-flags "cvs update flags"))) | ||
| 925 | (when (eq flags t) | ||
| 926 | (setf flags (cvs-flags-query 'cvs-update-flags nil 'noquery))) | ||
| 927 | (cvs-cmd-do "update" directory flags nil | ||
| 928 | (> (prefix-numeric-value current-prefix-arg) 8))) | ||
| 929 | |||
| 930 | |||
| 931 | ;;;###autoload | ||
| 932 | (defun cvs-status (directory flags &optional noshow) | ||
| 933 | "Run a `cvs status' in the current working DIRECTORY. | ||
| 934 | Feed the output to a *cvs* buffer and run `cvs-mode' on it. | ||
| 935 | With a prefix argument, prompt for a directory and cvs FLAGS to use. | ||
| 936 | A prefix arg >8 (ex: \\[universal-argument] \\[universal-argument]), | ||
| 937 | prevents reuse of an existing *cvs* buffer. | ||
| 938 | Optional argument NOSHOW if non-nil means not to display the buffer." | ||
| 939 | (interactive (list (cvs-query-directory "CVS Status (directory): ") | ||
| 940 | (cvs-flags-query 'cvs-status-flags "cvs status flags"))) | ||
| 941 | (when (eq flags t) | ||
| 942 | (setf flags (cvs-flags-query 'cvs-status-flags nil 'noquery))) | ||
| 943 | (cvs-cmd-do "status" directory flags nil | ||
| 944 | (> (prefix-numeric-value current-prefix-arg) 8) | ||
| 945 | :noshow noshow :dont-change-disc t)) | ||
| 946 | |||
| 947 | ;;---------- | ||
| 948 | (defun cvs-update-filter (proc string) | ||
| 949 | "Filter function for pcl-cvs. | ||
| 950 | This function gets the output that CVS sends to stdout. It inserts | ||
| 951 | the STRING into (process-buffer PROC) but it also checks if CVS is waiting | ||
| 952 | for a lock file. If so, it inserts a message cookie in the *cvs* buffer." | ||
| 953 | (save-match-data | ||
| 954 | (with-current-buffer (process-buffer proc) | ||
| 955 | (let ((inhibit-read-only t)) | ||
| 956 | (save-excursion | ||
| 957 | ;; Insert the text, moving the process-marker. | ||
| 958 | (goto-char (process-mark proc)) | ||
| 959 | (insert string) | ||
| 960 | (set-marker (process-mark proc) (point)) | ||
| 961 | ;; FIXME: Delete any old lock message | ||
| 962 | ;;(if (tin-nth cookies 1) | ||
| 963 | ;; (tin-delete cookies | ||
| 964 | ;; (tin-nth cookies 1))) | ||
| 965 | ;; Check if CVS is waiting for a lock. | ||
| 966 | (beginning-of-line 0) ;Move to beginning of last complete line. | ||
| 967 | (when (looking-at "^[ a-z]+: \\(.*waiting for .*lock in \\(.*\\)\\)$") | ||
| 968 | (let ((msg (match-string 1)) | ||
| 969 | (lock (match-string 2))) | ||
| 970 | (with-current-buffer cvs-buffer | ||
| 971 | (set (make-local-variable 'cvs-lock-file) lock) | ||
| 972 | ;; display the lock situation in the *cvs* buffer: | ||
| 973 | (ewoc-enter-last | ||
| 974 | cvs-cookies | ||
| 975 | (cvs-create-fileinfo | ||
| 976 | 'MESSAGE "" " " | ||
| 977 | (concat msg | ||
| 978 | (substitute-command-keys | ||
| 979 | "\n\t(type \\[cvs-mode-delete-lock] to delete it)")) | ||
| 980 | :subtype 'TEMP)) | ||
| 981 | (pop-to-buffer (current-buffer)) | ||
| 982 | (goto-char (point-max)) | ||
| 983 | (beep))))))))) | ||
| 984 | |||
| 985 | |||
| 986 | ;;;; | ||
| 987 | ;;;; The cvs-mode and its associated commands. | ||
| 988 | ;;;; | ||
| 989 | |||
| 990 | (cvs-prefix-define cvs-force-command "" "" '("/F") cvs-qtypedesc-string1) | ||
| 991 | (defun-cvs-mode cvs-mode-force-command (arg) | ||
| 992 | "Force the next cvs command to operate on all the selected files. | ||
| 993 | By default, cvs commands only operate on files on which the command | ||
| 994 | \"makes sense\". This overrides the safety feature on the next cvs command. | ||
| 995 | It actually behaves as a toggle. If prefixed by \\[universal-argument] \\[universal-argument], | ||
| 996 | the override will persist until the next toggle." | ||
| 997 | (interactive "P") | ||
| 998 | (cvs-prefix-set 'cvs-force-command arg)) | ||
| 999 | |||
| 1000 | ;;---------- | ||
| 1001 | (put 'cvs-mode 'mode-class 'special) | ||
| 1002 | (easy-mmode-define-derived-mode cvs-mode fundamental-mode "CVS" | ||
| 1003 | "Mode used for PCL-CVS, a frontend to CVS. | ||
| 1004 | Full documentation is in the Texinfo file. | ||
| 1005 | Pcl-cvs runs `pcl-cvs-load-hook' after being loaded." | ||
| 1006 | (setq mode-line-process | ||
| 1007 | '("" cvs-force-command cvs-ignore-marks-modif | ||
| 1008 | ":" (cvs-branch-prefix | ||
| 1009 | ("" cvs-branch-prefix (cvs-secondary-branch-prefix | ||
| 1010 | ("->" cvs-secondary-branch-prefix)))) | ||
| 1011 | " " cvs-mode-line-process)) | ||
| 1012 | (buffer-disable-undo (current-buffer)) | ||
| 1013 | ;;(set (make-local-variable 'goal-column) cvs-cursor-column) | ||
| 1014 | (set (make-local-variable 'revert-buffer-function) 'cvs-mode-revert-buffer) | ||
| 1015 | (cvs-prefix-make-local 'cvs-branch-prefix) | ||
| 1016 | (cvs-prefix-make-local 'cvs-secondary-branch-prefix) | ||
| 1017 | (cvs-prefix-make-local 'cvs-force-command) | ||
| 1018 | (cvs-prefix-make-local 'cvs-ignore-marks-modif) | ||
| 1019 | (make-local-variable 'cvs-mode-line-process) | ||
| 1020 | (make-local-variable 'cvs-temp-buffers)) | ||
| 1021 | |||
| 1022 | |||
| 1023 | (defun cvs-buffer-p (&optional buffer) | ||
| 1024 | "Return whether the (by default current) BUFFER is a `cvs-mode' buffer." | ||
| 1025 | (save-excursion | ||
| 1026 | (if buffer (set-buffer buffer)) | ||
| 1027 | (and (eq major-mode 'cvs-mode)))) | ||
| 1028 | |||
| 1029 | (defun cvs-buffer-check () | ||
| 1030 | "Check that the current buffer follows cvs-buffer's conventions." | ||
| 1031 | (let ((buf (current-buffer)) | ||
| 1032 | (check 'none)) | ||
| 1033 | (or (and (setq check 'collection) | ||
| 1034 | (eq (ewoc-buffer cvs-cookies) buf) | ||
| 1035 | (setq check 'cvs-temp-buffer) | ||
| 1036 | (or (null cvs-temp-buffer) | ||
| 1037 | (null (buffer-name cvs-temp-buffer)) | ||
| 1038 | (and (eq (with-current-buffer cvs-temp-buffer cvs-buffer) buf) | ||
| 1039 | (equal (with-current-buffer cvs-temp-buffer | ||
| 1040 | default-directory) | ||
| 1041 | default-directory))) | ||
| 1042 | t) | ||
| 1043 | (error "Inconsistent %s in buffer %s" check (buffer-name buf))))) | ||
| 1044 | |||
| 1045 | |||
| 1046 | (defun-cvs-mode cvs-mode-quit () | ||
| 1047 | "Quit PCL-CVS, killing the *cvs* buffer." | ||
| 1048 | (interactive) | ||
| 1049 | (and (y-or-n-p "Quit pcl-cvs? ") (kill-buffer (current-buffer)))) | ||
| 1050 | |||
| 1051 | ;; Give help.... | ||
| 1052 | |||
| 1053 | (defun cvs-help () | ||
| 1054 | "Display help for various PCL-CVS commands." | ||
| 1055 | (interactive) | ||
| 1056 | (if (eq last-command 'cvs-help) | ||
| 1057 | (describe-function 'cvs-mode) ; would need to use minor-mode for cvs-edit-mode | ||
| 1058 | (message | ||
| 1059 | (substitute-command-keys | ||
| 1060 | "`\\[cvs-help]':help `\\[cvs-mode-add]':add `\\[cvs-mode-commit]':commit \ | ||
| 1061 | `\\[cvs-mode-diff-map]':diff* `\\[cvs-mode-log]':log \ | ||
| 1062 | `\\[cvs-mode-remove]':remove `\\[cvs-mode-status]':status \ | ||
| 1063 | `\\[cvs-mode-undo]':undo")))) | ||
| 1064 | |||
| 1065 | (defun cvs-mode-diff-help () | ||
| 1066 | "Display help for various PCL-CVS diff commands." | ||
| 1067 | (interactive) | ||
| 1068 | (if (eq last-command 'cvs-mode-diff-help) | ||
| 1069 | (describe-function 'cvs-mode) ; no better docs for diff stuff? | ||
| 1070 | (message | ||
| 1071 | (substitute-command-keys | ||
| 1072 | "`\\[cvs-mode-diff]':diff `\\[cvs-mode-idiff]':idiff \ | ||
| 1073 | `\\[cvs-mode-diff-head]':head `\\[cvs-mode-diff-vendor]':vendor \ | ||
| 1074 | `\\[cvs-mode-diff-backup]':backup `\\[cvs-mode-idiff-other]':other \ | ||
| 1075 | `\\[cvs-mode-imerge]':imerge")))) | ||
| 1076 | |||
| 1077 | ;; Move around in the buffer | ||
| 1078 | |||
| 1079 | (defun-cvs-mode cvs-mode-previous-line (arg) | ||
| 1080 | "Go to the previous line. | ||
| 1081 | If a prefix argument is given, move by that many lines." | ||
| 1082 | (interactive "p") | ||
| 1083 | (ewoc-goto-prev cvs-cookies (point) arg)) | ||
| 1084 | |||
| 1085 | (defun-cvs-mode cvs-mode-next-line (arg) | ||
| 1086 | "Go to the next line. | ||
| 1087 | If a prefix argument is given, move by that many lines." | ||
| 1088 | (interactive "p") | ||
| 1089 | (ewoc-goto-next cvs-cookies (point) arg)) | ||
| 1090 | |||
| 1091 | ;;;; | ||
| 1092 | ;;;; Mark handling | ||
| 1093 | ;;;; | ||
| 1094 | |||
| 1095 | (defun-cvs-mode cvs-mode-mark (&optional arg) | ||
| 1096 | "Mark the fileinfo on the current line. | ||
| 1097 | If the fileinfo is a directory, all the contents of that directory are | ||
| 1098 | marked instead. A directory can never be marked." | ||
| 1099 | (interactive) | ||
| 1100 | (let* ((tin (ewoc-locate cvs-cookies (point))) | ||
| 1101 | (fi (ewoc-data tin))) | ||
| 1102 | (if (eq (cvs-fileinfo->type fi) 'DIRCHANGE) | ||
| 1103 | ;; it's a directory: let's mark all files inside | ||
| 1104 | (ewoc-map | ||
| 1105 | (lambda (f dir) | ||
| 1106 | (when (cvs-dir-member-p f dir) | ||
| 1107 | (setf (cvs-fileinfo->marked f) | ||
| 1108 | (not (if (eq arg 'toggle) (cvs-fileinfo->marked f) arg))) | ||
| 1109 | t)) ;Tell cookie to redisplay this cookie. | ||
| 1110 | cvs-cookies | ||
| 1111 | (cvs-fileinfo->dir fi)) | ||
| 1112 | ;; not a directory: just do the obvious | ||
| 1113 | (setf (cvs-fileinfo->marked fi) | ||
| 1114 | (not (if (eq arg 'toggle) (cvs-fileinfo->marked fi) arg))) | ||
| 1115 | (ewoc-invalidate cvs-cookies tin) | ||
| 1116 | (cvs-mode-next-line 1)))) | ||
| 1117 | |||
| 1118 | (defun cvs-mouse-toggle-mark (e) | ||
| 1119 | "Toggle the mark of the entry under the mouse." | ||
| 1120 | (interactive "e") | ||
| 1121 | (mouse-set-point e) | ||
| 1122 | (cvs-mode-mark 'toggle)) | ||
| 1123 | |||
| 1124 | (defun-cvs-mode cvs-mode-unmark () | ||
| 1125 | "Unmark the fileinfo on the current line." | ||
| 1126 | (interactive) | ||
| 1127 | (cvs-mode-mark t)) | ||
| 1128 | |||
| 1129 | (defun-cvs-mode cvs-mode-mark-all-files () | ||
| 1130 | "Mark all files." | ||
| 1131 | (interactive) | ||
| 1132 | (ewoc-map (lambda (cookie) | ||
| 1133 | (unless (eq (cvs-fileinfo->type cookie) 'DIRCHANGE) | ||
| 1134 | (setf (cvs-fileinfo->marked cookie) t))) | ||
| 1135 | cvs-cookies)) | ||
| 1136 | |||
| 1137 | (defun-cvs-mode cvs-mode-mark-matching-files (regex) | ||
| 1138 | "Mark all files matching REGEX." | ||
| 1139 | (interactive "sMark files matching: ") | ||
| 1140 | (ewoc-map (lambda (cookie) | ||
| 1141 | (when (and (not (eq (cvs-fileinfo->type cookie) 'DIRCHANGE)) | ||
| 1142 | (string-match regex (cvs-fileinfo->file cookie))) | ||
| 1143 | (setf (cvs-fileinfo->marked cookie) t))) | ||
| 1144 | cvs-cookies)) | ||
| 1145 | |||
| 1146 | (defun-cvs-mode cvs-mode-unmark-all-files () | ||
| 1147 | "Unmark all files. | ||
| 1148 | Directories are also unmarked, but that doesn't matter, since | ||
| 1149 | they should always be unmarked." | ||
| 1150 | (interactive) | ||
| 1151 | (ewoc-map (lambda (cookie) | ||
| 1152 | (setf (cvs-fileinfo->marked cookie) nil) | ||
| 1153 | t) | ||
| 1154 | cvs-cookies)) | ||
| 1155 | |||
| 1156 | (defun-cvs-mode cvs-mode-unmark-up () | ||
| 1157 | "Unmark the file on the previous line." | ||
| 1158 | (interactive) | ||
| 1159 | (let ((tin (ewoc-goto-prev cvs-cookies (point) 1))) | ||
| 1160 | (when tin | ||
| 1161 | (setf (cvs-fileinfo->marked (ewoc-data tin)) nil) | ||
| 1162 | (ewoc-invalidate cvs-cookies tin)))) | ||
| 1163 | |||
| 1164 | (defconst cvs-ignore-marks-alternatives | ||
| 1165 | '(("toggle-marks" . "/TM") | ||
| 1166 | ("force-marks" . "/FM") | ||
| 1167 | ("ignore-marks" . "/IM"))) | ||
| 1168 | |||
| 1169 | (cvs-prefix-define cvs-ignore-marks-modif | ||
| 1170 | "Prefix to decide whether to ignore marks or not." | ||
| 1171 | "active" | ||
| 1172 | (mapcar 'cdr cvs-ignore-marks-alternatives) | ||
| 1173 | (cvs-qtypedesc-create | ||
| 1174 | (lambda (str) (cdr (assoc str cvs-ignore-marks-alternatives))) | ||
| 1175 | (lambda (obj) (caar (member* obj cvs-ignore-marks-alternatives :key 'cdr))) | ||
| 1176 | (lambda () cvs-ignore-marks-alternatives) | ||
| 1177 | nil t)) | ||
| 1178 | |||
| 1179 | (defun-cvs-mode cvs-mode-toggle-marks (arg) | ||
| 1180 | "Toggle whether the next CVS command uses marks. | ||
| 1181 | See `cvs-prefix-set' for further description of the behavior. | ||
| 1182 | \\[universal-argument] 1 selects `force-marks', | ||
| 1183 | \\[universal-argument] 2 selects `ignore-marks', | ||
| 1184 | \\[universal-argument] 3 selects `toggle-marks'." | ||
| 1185 | (interactive "P") | ||
| 1186 | (cvs-prefix-set 'cvs-ignore-marks-modif arg)) | ||
| 1187 | |||
| 1188 | (defun cvs-ignore-marks-p (cmd &optional read-only) | ||
| 1189 | (let ((default (if (member cmd cvs-invert-ignore-marks) | ||
| 1190 | (not cvs-default-ignore-marks) | ||
| 1191 | cvs-default-ignore-marks)) | ||
| 1192 | (modif (cvs-prefix-get 'cvs-ignore-marks-modif read-only))) | ||
| 1193 | (cond | ||
| 1194 | ((equal modif "/IM") t) | ||
| 1195 | ((equal modif "/TM") (not default)) | ||
| 1196 | ((equal modif "/FM") nil) | ||
| 1197 | (t default)))) | ||
| 1198 | |||
| 1199 | (defun cvs-mode-mark-get-modif (cmd) | ||
| 1200 | (if (cvs-ignore-marks-p cmd 'read-only) "/IM" "/FM")) | ||
| 1201 | |||
| 1202 | (defvar cvs-minor-current-files) | ||
| 1203 | (defun cvs-get-marked (&optional ignore-marks ignore-contents) | ||
| 1204 | "Return a list of all selected fileinfos. | ||
| 1205 | If there are any marked tins, and IGNORE-MARKS is nil, return them. | ||
| 1206 | Otherwise, if the cursor selects a directory, and IGNORE-CONTENTS is | ||
| 1207 | nil, return all files in it, else return just the directory. | ||
| 1208 | Otherwise return (a list containing) the file the cursor points to, or | ||
| 1209 | an empty list if it doesn't point to a file at all. | ||
| 1210 | |||
| 1211 | Args: &optional IGNORE-MARKS IGNORE-CONTENTS." | ||
| 1212 | |||
| 1213 | (let ((fis nil)) | ||
| 1214 | (dolist (fi (if (boundp 'cvs-minor-current-files) | ||
| 1215 | (mapcar | ||
| 1216 | (lambda (f) | ||
| 1217 | (let ((f (file-relative-name f))) | ||
| 1218 | (if (file-directory-p f) | ||
| 1219 | (cvs-create-fileinfo | ||
| 1220 | 'DIRCHANGE (file-name-as-directory f) "." "") | ||
| 1221 | (let ((dir (file-name-directory f)) | ||
| 1222 | (file (file-name-nondirectory f))) | ||
| 1223 | (cvs-create-fileinfo | ||
| 1224 | 'UNKNOWN (or dir "") file ""))))) | ||
| 1225 | cvs-minor-current-files) | ||
| 1226 | (or (and (not ignore-marks) | ||
| 1227 | (ewoc-collect cvs-cookies | ||
| 1228 | 'cvs-fileinfo->marked)) | ||
| 1229 | (list (ewoc-data (ewoc-locate cvs-cookies (point))))))) | ||
| 1230 | |||
| 1231 | (if (or ignore-contents (not (eq (cvs-fileinfo->type fi) 'DIRCHANGE))) | ||
| 1232 | (push fi fis) | ||
| 1233 | ;; If a directory is selected, return members, if any. | ||
| 1234 | (setq fis | ||
| 1235 | (append (ewoc-collect cvs-cookies | ||
| 1236 | 'cvs-dir-member-p | ||
| 1237 | (cvs-fileinfo->dir fi)) | ||
| 1238 | fis)))) | ||
| 1239 | (nreverse fis))) | ||
| 1240 | |||
| 1241 | (defun* cvs-mode-marked (filter &optional (cmd (symbol-name filter)) | ||
| 1242 | &key read-only one file) | ||
| 1243 | "Get the list of marked FIS. | ||
| 1244 | CMD is used to determine whether to use the marks or not. | ||
| 1245 | Only files for which FILTER is applicable are returned. | ||
| 1246 | If READ-ONLY is non-nil, the current toggling is left intact. | ||
| 1247 | If ONE is non-nil, marks are ignored and a single FI is returned. | ||
| 1248 | If FILE is non-nil, directory entries won't be selected." | ||
| 1249 | (let* ((fis (cvs-get-marked (or one (cvs-ignore-marks-p cmd read-only)) | ||
| 1250 | (and (not file) | ||
| 1251 | (cvs-applicable-p 'DIRCHANGE filter)))) | ||
| 1252 | (force (cvs-prefix-get 'cvs-force-command)) | ||
| 1253 | (fis (car (cvs-partition | ||
| 1254 | (lambda (fi) (cvs-applicable-p fi (and (not force) filter))) | ||
| 1255 | fis)))) | ||
| 1256 | (cond | ||
| 1257 | ((null fis) | ||
| 1258 | (error "`%s' is not applicable to any of the selected files." filter)) | ||
| 1259 | ((and one (cdr fis)) | ||
| 1260 | (error "`%s' is only applicable to a single file." cmd)) | ||
| 1261 | (one (car fis)) | ||
| 1262 | (t fis)))) | ||
| 1263 | |||
| 1264 | (defun cvs-enabledp (filter) | ||
| 1265 | "Determine whether FILTER applies to at least one of the selected files." | ||
| 1266 | (ignore-errors (cvs-mode-marked filter nil :read-only t))) | ||
| 1267 | |||
| 1268 | (defun cvs-mode-files (&rest -cvs-mode-files-args) | ||
| 1269 | (cvs-mode! | ||
| 1270 | (lambda () | ||
| 1271 | (mapcar 'cvs-fileinfo->full-path | ||
| 1272 | (apply 'cvs-mode-marked -cvs-mode-files-args))))) | ||
| 1273 | |||
| 1274 | ;;; | ||
| 1275 | ;;; Interface between CVS-Edit and PCL-CVS | ||
| 1276 | ;;; | ||
| 1277 | |||
| 1278 | (defun cvs-mode-commit-setup () | ||
| 1279 | "Run `cvs-mode-commit' with setup." | ||
| 1280 | (interactive) | ||
| 1281 | (cvs-mode-commit 'force)) | ||
| 1282 | |||
| 1283 | (defun cvs-mode-commit (setup) | ||
| 1284 | "Check in all marked files, or the current file. | ||
| 1285 | The user will be asked for a log message in a buffer. | ||
| 1286 | The buffer's mode and name is determined by the \"message\" setting | ||
| 1287 | of `cvs-buffer-name-alist'. | ||
| 1288 | The POSTPROC specified there (typically `cvs-edit') is then called, | ||
| 1289 | passing it the SETUP argument." | ||
| 1290 | (interactive "P") | ||
| 1291 | ;; It seems that the save-excursion that happens if I use the better | ||
| 1292 | ;; form of `(cvs-mode! (lambda ...))' screws up a couple things which | ||
| 1293 | ;; end up being rather annoying (like cvs-edit-mode's message being | ||
| 1294 | ;; displayed in the wrong minibuffer). | ||
| 1295 | (cvs-mode!) | ||
| 1296 | (pop-to-buffer (cvs-temp-buffer "message" 'normal 'nosetup)) | ||
| 1297 | (set (make-local-variable 'cvs-minor-wrap-function) 'cvs-commit-minor-wrap) | ||
| 1298 | (let ((lbd list-buffers-directory) | ||
| 1299 | (setupfun (or (third (cdr (assoc "message" cvs-buffer-name-alist))) | ||
| 1300 | 'cvs-edit))) | ||
| 1301 | (funcall setupfun 'cvs-do-commit setup 'cvs-commit-filelist) | ||
| 1302 | (set (make-local-variable 'list-buffers-directory) lbd))) | ||
| 1303 | |||
| 1304 | (defun cvs-commit-minor-wrap (buf f) | ||
| 1305 | (let ((cvs-ignore-marks-modif (cvs-mode-mark-get-modif "commit"))) | ||
| 1306 | (funcall f))) | ||
| 1307 | |||
| 1308 | (defun cvs-commit-filelist () (cvs-mode-files 'commit nil :read-only t :file t)) | ||
| 1309 | |||
| 1310 | (defun cvs-do-commit (flags) | ||
| 1311 | "Do the actual commit, using the current buffer as the log message." | ||
| 1312 | (interactive (list (cvs-flags-query 'cvs-commit-flags "cvs commit flags"))) | ||
| 1313 | (let ((msg (buffer-string))) | ||
| 1314 | (cvs-mode!) | ||
| 1315 | ;;(pop-to-buffer cvs-buffer) | ||
| 1316 | (cvs-mode-do "commit" (list* "-m" msg flags) 'commit))) | ||
| 1317 | |||
| 1318 | |||
| 1319 | ;;;; | ||
| 1320 | ;;;; CVS Mode commands | ||
| 1321 | ;;;; | ||
| 1322 | |||
| 1323 | (defun-cvs-mode (cvs-mode-insert . NOARGS) (file) | ||
| 1324 | "Insert an entry for a specific file." | ||
| 1325 | (interactive | ||
| 1326 | (list (read-file-name "File to insert: " nil nil nil | ||
| 1327 | (ignore-errors | ||
| 1328 | (cvs-fileinfo->dir | ||
| 1329 | (car (cvs-mode-marked nil nil :read-only t))))))) | ||
| 1330 | (let ((file (file-relative-name (directory-file-name file)))) | ||
| 1331 | (if (file-directory-p file) | ||
| 1332 | (let ((fi (cvs-create-fileinfo 'DIRCHANGE | ||
| 1333 | (file-name-as-directory file) | ||
| 1334 | "." | ||
| 1335 | "cvs-mode-insert"))) | ||
| 1336 | (cvs-addto-collection cvs-cookies fi)) | ||
| 1337 | (let ((fi (cvs-create-fileinfo 'UNKNOWN | ||
| 1338 | (or (file-name-directory file) "") | ||
| 1339 | (file-name-nondirectory file) | ||
| 1340 | "cvs-mode-insert"))) | ||
| 1341 | (cvs-mode-run "status" (cvs-flags-query 'cvs-status-flags nil 'noquery) | ||
| 1342 | (list fi) :dont-change-disc t))))) | ||
| 1343 | |||
| 1344 | (defun-cvs-mode (cvs-mode-add . SIMPLE) (flags) | ||
| 1345 | "Add marked files to the cvs repository. | ||
| 1346 | With prefix argument, prompt for cvs flags." | ||
| 1347 | (interactive (list (cvs-flags-query 'cvs-add-flags "cvs add flags"))) | ||
| 1348 | (let ((fis (cvs-mode-marked 'add)) | ||
| 1349 | (needdesc nil) (dirs nil)) | ||
| 1350 | ;; find directories and look for fis needing a description | ||
| 1351 | (dolist (fi fis) | ||
| 1352 | (cond | ||
| 1353 | ((file-directory-p (cvs-fileinfo->full-path fi)) (push fi dirs)) | ||
| 1354 | ((eq (cvs-fileinfo->type fi) 'UNKNOWN) (setq needdesc t)))) | ||
| 1355 | ;; prompt for description if necessary | ||
| 1356 | (let* ((msg (if (and needdesc | ||
| 1357 | (or current-prefix-arg (not cvs-add-default-message))) | ||
| 1358 | (read-from-minibuffer "Enter description: ") | ||
| 1359 | (or cvs-add-default-message ""))) | ||
| 1360 | (flags (list* "-m" msg flags)) | ||
| 1361 | (postproc | ||
| 1362 | ;; setup postprocessing for the directory entries | ||
| 1363 | (when dirs | ||
| 1364 | `((cvs-run-process (list "-n" "update") | ||
| 1365 | ',dirs | ||
| 1366 | '(cvs-parse-process t)) | ||
| 1367 | (dolist (fi ',dirs) (setf (cvs-fileinfo->type fi) 'DEAD)))))) | ||
| 1368 | (cvs-mode-run "add" flags fis :postproc postproc)))) | ||
| 1369 | |||
| 1370 | ;;---------- | ||
| 1371 | (defun-cvs-mode (cvs-mode-diff . DOUBLE) (flags) | ||
| 1372 | "Diff the selected files against the repository. | ||
| 1373 | This command compares the files in your working area against the | ||
| 1374 | revision which they are based upon." | ||
| 1375 | (interactive | ||
| 1376 | (list (cvs-add-branch-prefix | ||
| 1377 | (cvs-add-secondary-branch-prefix | ||
| 1378 | (cvs-flags-query 'cvs-diff-flags "cvs diff flags"))))) | ||
| 1379 | (cvs-mode-do "diff" flags 'diff | ||
| 1380 | :show t)) ;; :ignore-exit t | ||
| 1381 | |||
| 1382 | ;;---------- | ||
| 1383 | (defun-cvs-mode (cvs-mode-diff-head . SIMPLE) (flags) | ||
| 1384 | "Diff the selected files against the head of the current branch. | ||
| 1385 | See ``cvs-mode-diff'' for more info." | ||
| 1386 | (interactive (list (cvs-flags-query 'cvs-diff-flags "cvs diff flags"))) | ||
| 1387 | (cvs-mode-diff-1 (cons "-rHEAD" flags))) | ||
| 1388 | |||
| 1389 | ;;---------- | ||
| 1390 | (defun-cvs-mode (cvs-mode-diff-vendor . SIMPLE) (flags) | ||
| 1391 | "Diff the selected files against the head of the vendor branch. | ||
| 1392 | See ``cvs-mode-diff'' for more info." | ||
| 1393 | (interactive (list (cvs-flags-query 'cvs-diff-flags "cvs diff flags"))) | ||
| 1394 | (cvs-mode-diff-1 (cons (concat "-r" cvs-vendor-branch) flags))) | ||
| 1395 | |||
| 1396 | ;;---------- | ||
| 1397 | ;; sadly, this is not provided by cvs, so we have to roll our own | ||
| 1398 | (defun-cvs-mode (cvs-mode-diff-backup . SIMPLE) (flags) | ||
| 1399 | "Diff the files against the backup file. | ||
| 1400 | This command can be used on files that are marked with \"Merged\" | ||
| 1401 | or \"Conflict\" in the *cvs* buffer." | ||
| 1402 | (interactive (list (cvs-flags-query 'cvs-diff-flags "diff flags"))) | ||
| 1403 | (unless (listp flags) (error "flags should be a list of strings.")) | ||
| 1404 | (save-some-buffers) | ||
| 1405 | (let* ((filter 'diff) | ||
| 1406 | (marked (cvs-get-marked (cvs-ignore-marks-p "diff"))) | ||
| 1407 | ;;(tins (cvs-filter-applicable filter marked)) | ||
| 1408 | (fis (delete-if-not 'cvs-fileinfo->backup-file marked))) | ||
| 1409 | (unless (consp fis) | ||
| 1410 | (error "No files with a backup file selected!")) | ||
| 1411 | ;; let's extract some info into the environment for `buffer-name' | ||
| 1412 | (let* ((dir (cvs-fileinfo->dir (car fis))) | ||
| 1413 | (file (cvs-fileinfo->file (car fis)))) | ||
| 1414 | (set-buffer (cvs-temp-buffer "diff"))) | ||
| 1415 | (message "cvs diff backup...") | ||
| 1416 | (cvs-execute-single-file-list fis 'cvs-diff-backup-extractor | ||
| 1417 | cvs-diff-program flags)) | ||
| 1418 | (message "cvs diff backup... Done.")) | ||
| 1419 | |||
| 1420 | ;;---------- | ||
| 1421 | ;; (defun cvs-backup-diffable-p (fi) | ||
| 1422 | ;; "Check if the TIN is backup-diffable. | ||
| 1423 | ;; It must have a backup file to be diffable." | ||
| 1424 | ;; (cvs-fileinfo->backup-file fi)) | ||
| 1425 | |||
| 1426 | ;;---------- | ||
| 1427 | (defun cvs-diff-backup-extractor (fileinfo) | ||
| 1428 | "Return the filename and the name of the backup file as a list. | ||
| 1429 | Signal an error if there is no backup file." | ||
| 1430 | (let ((backup-file (cvs-fileinfo->backup-file fileinfo))) | ||
| 1431 | (unless backup-file | ||
| 1432 | (error "%s has no backup file." (cvs-fileinfo->full-path fileinfo))) | ||
| 1433 | (list backup-file (cvs-fileinfo->file fileinfo)))) | ||
| 1434 | |||
| 1435 | ;; | ||
| 1436 | ;; Emerge support | ||
| 1437 | ;; | ||
| 1438 | (defun cvs-emerge-diff (b1 b2) (emerge-buffers b1 b2 b1)) | ||
| 1439 | (defun cvs-emerge-merge (b1 b2 base out) | ||
| 1440 | (emerge-buffers-with-ancestor b1 b2 base (find-file-noselect out))) | ||
| 1441 | |||
| 1442 | ;; | ||
| 1443 | ;; Ediff support | ||
| 1444 | ;; | ||
| 1445 | |||
| 1446 | (defvar ediff-after-quit-destination-buffer) | ||
| 1447 | (defvar cvs-transient-buffers) | ||
| 1448 | (defun cvs-ediff-startup-hook () | ||
| 1449 | (add-hook 'ediff-after-quit-hook-internal | ||
| 1450 | `(lambda () | ||
| 1451 | (cvs-ediff-exit-hook | ||
| 1452 | ',ediff-after-quit-destination-buffer ',cvs-transient-buffers)) | ||
| 1453 | nil 'local)) | ||
| 1454 | |||
| 1455 | (defun cvs-ediff-exit-hook (cvs-buf tmp-bufs) | ||
| 1456 | ;; kill the temp buffers (and their associated windows) | ||
| 1457 | (dolist (tb tmp-bufs) | ||
| 1458 | (when (and tb (buffer-live-p tb) (not (buffer-modified-p tb))) | ||
| 1459 | (let ((win (get-buffer-window tb t))) | ||
| 1460 | (when win (delete-window win)) | ||
| 1461 | (kill-buffer tb)))) | ||
| 1462 | ;; switch back to the *cvs* buffer | ||
| 1463 | (when (and cvs-buf (buffer-live-p cvs-buf) | ||
| 1464 | (not (get-buffer-window cvs-buf t))) | ||
| 1465 | (ignore-errors (switch-to-buffer cvs-buf)))) | ||
| 1466 | |||
| 1467 | (defun cvs-ediff-diff (b1 b2) | ||
| 1468 | (let ((ediff-after-quit-destination-buffer (current-buffer)) | ||
| 1469 | (startup-hook '(cvs-ediff-startup-hook))) | ||
| 1470 | (ediff-buffers b1 b2 startup-hook 'ediff-revisions))) | ||
| 1471 | |||
| 1472 | (defun cvs-ediff-merge (b1 b2 base out) | ||
| 1473 | (let ((ediff-after-quit-destination-buffer (current-buffer)) | ||
| 1474 | (startup-hook '(cvs-ediff-startup-hook))) | ||
| 1475 | (ediff-merge-buffers-with-ancestor | ||
| 1476 | b1 b2 base startup-hook | ||
| 1477 | 'ediff-merge-revisions-with-ancestor | ||
| 1478 | out))) | ||
| 1479 | |||
| 1480 | ;; | ||
| 1481 | ;; Interactive merge/diff support. | ||
| 1482 | ;; | ||
| 1483 | |||
| 1484 | (defun cvs-retrieve-revision (fileinfo rev) | ||
| 1485 | "Retrieve the given REVision of the file in FILEINFO into a new buffer." | ||
| 1486 | (save-excursion | ||
| 1487 | (let* ((file (cvs-fileinfo->full-path fileinfo)) | ||
| 1488 | (buf (create-file-buffer (concat file "." rev)))) | ||
| 1489 | (set-buffer buf) | ||
| 1490 | (message "Retrieving revision %s..." rev) | ||
| 1491 | (let ((res (call-process cvs-program nil t nil | ||
| 1492 | "-q" "update" "-p" "-r" rev file))) | ||
| 1493 | (when (and res (not (and (equal 0 res)))) | ||
| 1494 | (error "Something went wrong retrieving revision %s: %s" rev res)) | ||
| 1495 | (set-buffer-modified-p nil) | ||
| 1496 | (let ((buffer-file-name (expand-file-name file))) | ||
| 1497 | (after-find-file)) | ||
| 1498 | (toggle-read-only 1) | ||
| 1499 | (message "Retrieving revision %s... Done" rev) | ||
| 1500 | buf)))) | ||
| 1501 | |||
| 1502 | (eval-and-compile (autoload 'vc-resolve-conflicts "vc")) | ||
| 1503 | |||
| 1504 | (defun-cvs-mode cvs-mode-imerge () | ||
| 1505 | "Merge interactively appropriate revisions of the selected file." | ||
| 1506 | (interactive) | ||
| 1507 | (let ((fi (cvs-mode-marked 'merge nil :one t :file t))) | ||
| 1508 | (let ((merge (cvs-fileinfo->merge fi)) | ||
| 1509 | (file (cvs-fileinfo->full-path fi)) | ||
| 1510 | (backup-file (cvs-fileinfo->backup-file fi))) | ||
| 1511 | (if (not (and merge backup-file)) | ||
| 1512 | (let ((buf (find-file-noselect file))) | ||
| 1513 | (message "Missing merge info or backup file, using VC.") | ||
| 1514 | (save-excursion | ||
| 1515 | (set-buffer buf) | ||
| 1516 | (vc-resolve-conflicts))) | ||
| 1517 | (let* ((ancestor-buf (cvs-retrieve-revision fi (car merge))) | ||
| 1518 | (head-buf (cvs-retrieve-revision fi (cdr merge))) | ||
| 1519 | (backup-buf (let ((auto-mode-alist nil)) | ||
| 1520 | (find-file-noselect backup-file))) | ||
| 1521 | ;; this binding is used by cvs-ediff-startup-hook | ||
| 1522 | (cvs-transient-buffers (list ancestor-buf backup-buf head-buf))) | ||
| 1523 | (with-current-buffer backup-buf | ||
| 1524 | (let ((buffer-file-name (expand-file-name file))) | ||
| 1525 | (after-find-file))) | ||
| 1526 | (funcall (cdr cvs-idiff-imerge-handlers) | ||
| 1527 | backup-buf head-buf ancestor-buf file)))))) | ||
| 1528 | |||
| 1529 | (cvs-flags-define cvs-idiff-version | ||
| 1530 | (list "BASE" cvs-vendor-branch cvs-vendor-branch "BASE" "BASE") | ||
| 1531 | "version: " cvs-qtypedesc-tag) | ||
| 1532 | |||
| 1533 | (defun-cvs-mode (cvs-mode-idiff . NOARGS) (&optional rev1 rev2) | ||
| 1534 | "Diff interactively current file to revisions." | ||
| 1535 | (interactive | ||
| 1536 | (let* ((rev1 (cvs-prefix-get 'cvs-branch-prefix)) | ||
| 1537 | (rev2 (and rev1 (cvs-prefix-get 'cvs-secondary-branch-prefix)))) | ||
| 1538 | (list (or rev1 (cvs-flags-query 'cvs-idiff-version)) | ||
| 1539 | rev2))) | ||
| 1540 | (let ((fi (cvs-mode-marked 'diff "idiff" :one t :file t))) | ||
| 1541 | (let* ((file (cvs-fileinfo->full-path fi)) | ||
| 1542 | (rev1-buf (cvs-retrieve-revision fi (or rev1 "BASE"))) | ||
| 1543 | (rev2-buf (if rev2 (cvs-retrieve-revision fi rev2))) | ||
| 1544 | ;; this binding is used by cvs-ediff-startup-hook | ||
| 1545 | (cvs-transient-buffers (list rev1-buf rev2-buf))) | ||
| 1546 | (funcall (car cvs-idiff-imerge-handlers) | ||
| 1547 | rev1-buf (or rev2-buf (find-file-noselect file)))))) | ||
| 1548 | |||
| 1549 | (defun-cvs-mode (cvs-mode-idiff-other . NOARGS) () | ||
| 1550 | "Diff interactively current file to revisions." | ||
| 1551 | (interactive) | ||
| 1552 | (let* ((rev1 (cvs-prefix-get 'cvs-branch-prefix)) | ||
| 1553 | (rev2 (and rev1 (cvs-prefix-get 'cvs-secondary-branch-prefix))) | ||
| 1554 | (fis (cvs-mode-marked 'diff "idiff" :file t))) | ||
| 1555 | (when (> (length fis) 2) | ||
| 1556 | (error "idiff-other cannot be applied to more than 2 files at a time.")) | ||
| 1557 | (let* ((fi1 (first fis)) | ||
| 1558 | (rev1-buf (if rev1 (cvs-retrieve-revision fi1 rev1) | ||
| 1559 | (find-file-noselect (cvs-fileinfo->full-path fi1)))) | ||
| 1560 | rev2-buf) | ||
| 1561 | (if (cdr fis) | ||
| 1562 | (let ((fi2 (second fis))) | ||
| 1563 | (setq rev2-buf | ||
| 1564 | (if rev2 (cvs-retrieve-revision fi2 rev2) | ||
| 1565 | (find-file-noselect (cvs-fileinfo->full-path fi2))))) | ||
| 1566 | (error "idiff-other doesn't know what other file/buffer to use.")) | ||
| 1567 | (let* (;; this binding is used by cvs-ediff-startup-hook | ||
| 1568 | (cvs-transient-buffers (list rev1-buf rev2-buf))) | ||
| 1569 | (funcall (car cvs-idiff-imerge-handlers) | ||
| 1570 | rev1-buf rev2-buf))))) | ||
| 1571 | |||
| 1572 | |||
| 1573 | (defun cvs-fileinfo-kill (c fi) | ||
| 1574 | "Mark a fileinfo xor its members (in case of a directory) as dead." | ||
| 1575 | (if (eq (cvs-fileinfo->type fi) 'DIRCHANGE) | ||
| 1576 | (dolist (fi (ewoc-collect c 'cvs-dir-member-p | ||
| 1577 | (cvs-fileinfo->dir fi))) | ||
| 1578 | (setf (cvs-fileinfo->type fi) 'DEAD)) | ||
| 1579 | (setf (cvs-fileinfo->type fi) 'DEAD))) | ||
| 1580 | |||
| 1581 | (defun* cvs-mode-run (cmd flags fis | ||
| 1582 | &key (buf (cvs-temp-buffer)) | ||
| 1583 | dont-change-disc cvsargs postproc) | ||
| 1584 | "Generic cvs-mode-<foo> function. | ||
| 1585 | Executes `cvs CVSARGS CMD FLAGS FIS'. | ||
| 1586 | BUF is the buffer to be used for cvs' output. | ||
| 1587 | DONT-CHANGE-DISC non-nil indicates that the command will not change the | ||
| 1588 | contents of files. This is only used by the parser. | ||
| 1589 | POSTPROC is a list of expressions to be evaluated at the very end (after | ||
| 1590 | parsing if applicable). It will be prepended with `progn' is necessary." | ||
| 1591 | (save-some-buffers) | ||
| 1592 | (unless (listp flags) (error "flags should be a list of strings")) | ||
| 1593 | (let* ((cvs-buf (current-buffer)) | ||
| 1594 | (single-dir (or (not (listp cvs-execute-single-dir)) | ||
| 1595 | (member cmd cvs-execute-single-dir))) | ||
| 1596 | (parse (member cmd cvs-parse-known-commands)) | ||
| 1597 | (args (append cvsargs (list cmd) flags)) | ||
| 1598 | (after-mode (third (cdr (assoc cmd cvs-buffer-name-alist))))) | ||
| 1599 | (cvs-cleanup-collection cvs-cookies ;cleanup remaining messages | ||
| 1600 | (eq cvs-auto-remove-handled 'delayed) nil t) | ||
| 1601 | (when (fboundp after-mode) | ||
| 1602 | (setq postproc (append postproc `((,after-mode))))) | ||
| 1603 | (when parse (push `(cvs-parse-process ',dont-change-disc) postproc)) | ||
| 1604 | (when (member cmd '("status" "update")) ;FIXME: Yuck!! | ||
| 1605 | ;; absence of `cvs update' output has a specific meaning. | ||
| 1606 | (push | ||
| 1607 | `(dolist (fi ',(or fis | ||
| 1608 | (list (cvs-create-fileinfo 'DIRCHANGE "" "." "")))) | ||
| 1609 | (cvs-fileinfo-kill ',cvs-cookies fi)) | ||
| 1610 | postproc)) | ||
| 1611 | (setq postproc (if (cdr postproc) (cons 'progn postproc) (car postproc))) | ||
| 1612 | (cvs-update-header args fis) | ||
| 1613 | (with-current-buffer buf | ||
| 1614 | ;;(set (make-local-variable 'cvs-buffer) cvs-buf) | ||
| 1615 | (let ((inhibit-read-only t)) (erase-buffer)) | ||
| 1616 | (message "Running cvs %s ..." cmd) | ||
| 1617 | (cvs-run-process args fis postproc single-dir)))) | ||
| 1618 | |||
| 1619 | |||
| 1620 | (defun* cvs-mode-do (cmd flags filter | ||
| 1621 | &key show dont-change-disc parse cvsargs postproc) | ||
| 1622 | "Generic cvs-mode-<foo> function. | ||
| 1623 | Executes `cvs CVSARGS CMD FLAGS' on the selected files. | ||
| 1624 | FILTER is passed to `cvs-applicable-p' to only apply the command to | ||
| 1625 | files for which it makes sense. | ||
| 1626 | SHOW indicates that CMD should be not be run in the default temp buffer and | ||
| 1627 | should be shown to the user. The buffer and mode to be used is determined | ||
| 1628 | by `cvs-buffer-name-alist'. | ||
| 1629 | DONT-CHANGE-DISC non-nil indicates that the command will not change the | ||
| 1630 | contents of files. This is only used by the parser." | ||
| 1631 | (cvs-mode-run cmd flags (cvs-mode-marked filter cmd) | ||
| 1632 | :buf (cvs-temp-buffer (when show cmd)) | ||
| 1633 | :dont-change-disc dont-change-disc | ||
| 1634 | :cvsargs cvsargs | ||
| 1635 | :postproc postproc)) | ||
| 1636 | |||
| 1637 | (defun-cvs-mode (cvs-mode-status . SIMPLE) (flags) | ||
| 1638 | "Show cvs status for all marked files. | ||
| 1639 | With prefix argument, prompt for cvs flags." | ||
| 1640 | (interactive (list (cvs-flags-query 'cvs-status-flags "cvs status flags"))) | ||
| 1641 | (cvs-mode-do "status" flags nil :dont-change-disc t :show t | ||
| 1642 | :postproc (when (eq cvs-auto-remove-handled 'status) | ||
| 1643 | '((with-current-buffer ,(current-buffer) | ||
| 1644 | (cvs-mode-remove-handled)))))) | ||
| 1645 | |||
| 1646 | (defun-cvs-mode (cvs-mode-tree . SIMPLE) (flags) | ||
| 1647 | "Call cvstree using the file under the point as a keyfile." | ||
| 1648 | (interactive (list (cvs-flags-query 'cvs-status-flags "cvs status flags"))) | ||
| 1649 | (cvs-mode-run "status" (cons "-v" flags) (cvs-mode-marked nil "status") | ||
| 1650 | :buf (cvs-temp-buffer "tree") | ||
| 1651 | :dont-change-disc t | ||
| 1652 | :postproc '((cvs-status-trees)))) | ||
| 1653 | |||
| 1654 | ;; cvs log | ||
| 1655 | |||
| 1656 | (defun-cvs-mode (cvs-mode-log . NOARGS) (flags) | ||
| 1657 | "Display the cvs log of all selected files. | ||
| 1658 | With prefix argument, prompt for cvs flags." | ||
| 1659 | (interactive (list (cvs-add-branch-prefix | ||
| 1660 | (cvs-flags-query 'cvs-log-flags "cvs log flags")))) | ||
| 1661 | (cvs-mode-do "log" flags nil :show t)) | ||
| 1662 | |||
| 1663 | |||
| 1664 | (defun-cvs-mode (cvs-mode-update . NOARGS) (flags) | ||
| 1665 | "Update all marked files. | ||
| 1666 | With a prefix argument, prompt for cvs flags." | ||
| 1667 | (interactive | ||
| 1668 | (list (cvs-add-branch-prefix | ||
| 1669 | (cvs-add-secondary-branch-prefix | ||
| 1670 | (cvs-flags-query 'cvs-update-flags "cvs update flags") | ||
| 1671 | "-j") "-j"))) | ||
| 1672 | (cvs-mode-do "update" flags 'update)) | ||
| 1673 | |||
| 1674 | |||
| 1675 | (defun-cvs-mode (cvs-mode-examine . NOARGS) (flags) | ||
| 1676 | "Re-examine all marked files. | ||
| 1677 | With a prefix argument, prompt for cvs flags." | ||
| 1678 | (interactive | ||
| 1679 | (list (cvs-add-branch-prefix | ||
| 1680 | (cvs-add-secondary-branch-prefix | ||
| 1681 | (cvs-flags-query 'cvs-update-flags "cvs -n update flags") | ||
| 1682 | "-j") "-j"))) | ||
| 1683 | (cvs-mode-do "update" flags nil :cvsargs '("-n") :dont-change-disc t)) | ||
| 1684 | |||
| 1685 | |||
| 1686 | (defun-cvs-mode cvs-mode-ignore (&optional pattern) | ||
| 1687 | "Arrange so that CVS ignores the selected files. | ||
| 1688 | This command ignores files that are not flagged as `Unknown'." | ||
| 1689 | (interactive) | ||
| 1690 | (dolist (fi (cvs-mode-marked 'ignore)) | ||
| 1691 | (cvs-append-to-ignore (cvs-fileinfo->dir fi) (cvs-fileinfo->file fi)) | ||
| 1692 | (setf (cvs-fileinfo->type fi) 'DEAD)) | ||
| 1693 | (cvs-cleanup-collection cvs-cookies nil nil nil)) | ||
| 1694 | |||
| 1695 | |||
| 1696 | (defun cvs-append-to-ignore (dir str) | ||
| 1697 | "Add STR to the .cvsignore file in DIR." | ||
| 1698 | (save-window-excursion | ||
| 1699 | (set-buffer (find-file-noselect (expand-file-name ".cvsignore" dir))) | ||
| 1700 | (when (ignore-errors | ||
| 1701 | (and buffer-read-only | ||
| 1702 | (eq 'CVS (vc-backend buffer-file-name)) | ||
| 1703 | (not (vc-locking-user buffer-file-name)))) | ||
| 1704 | ;; CVSREAD=on special case | ||
| 1705 | (vc-toggle-read-only)) | ||
| 1706 | (goto-char (point-max)) | ||
| 1707 | (unless (zerop (current-column)) (insert "\n")) | ||
| 1708 | (insert str "\n") | ||
| 1709 | (if cvs-sort-ignore-file (sort-lines nil (point-min) (point-max))) | ||
| 1710 | (save-buffer))) | ||
| 1711 | |||
| 1712 | |||
| 1713 | (defun cvs-mode-find-file-other-window (e) | ||
| 1714 | "Select a buffer containing the file in another window." | ||
| 1715 | (interactive (list last-input-event)) | ||
| 1716 | (cvs-mode-find-file e t)) | ||
| 1717 | |||
| 1718 | |||
| 1719 | (defun cvs-find-modif (fi) | ||
| 1720 | (with-temp-buffer | ||
| 1721 | (call-process cvs-program nil (current-buffer) nil | ||
| 1722 | "-f" "diff" (cvs-fileinfo->file fi)) | ||
| 1723 | (goto-char (point-min)) | ||
| 1724 | (if (re-search-forward "^\\([0-9]+\\)" nil t) | ||
| 1725 | (string-to-number (match-string 1)) | ||
| 1726 | 1))) | ||
| 1727 | |||
| 1728 | |||
| 1729 | (defun cvs-mode-find-file (e &optional other) | ||
| 1730 | "Select a buffer containing the file. | ||
| 1731 | With a prefix, opens the buffer in an OTHER window." | ||
| 1732 | (interactive (list last-input-event current-prefix-arg)) | ||
| 1733 | (ignore-errors (mouse-set-point e)) ;for invocation via the mouse | ||
| 1734 | (cvs-mode! | ||
| 1735 | (lambda (&optional rev) | ||
| 1736 | (interactive (list (cvs-prefix-get 'cvs-branch-prefix))) | ||
| 1737 | (let* ((cvs-buf (current-buffer)) | ||
| 1738 | (fi (cvs-mode-marked nil nil :one t))) | ||
| 1739 | (if (eq (cvs-fileinfo->type fi) 'DIRCHANGE) | ||
| 1740 | (let ((odir default-directory)) | ||
| 1741 | (setq default-directory | ||
| 1742 | (cvs-expand-dir-name (cvs-fileinfo->dir fi))) | ||
| 1743 | (if other | ||
| 1744 | (dired-other-window default-directory) | ||
| 1745 | (dired default-directory)) | ||
| 1746 | (set-buffer cvs-buf) | ||
| 1747 | (setq default-directory odir)) | ||
| 1748 | (let ((buf (if rev (cvs-retrieve-revision fi rev) | ||
| 1749 | (find-file-noselect (cvs-fileinfo->full-path fi))))) | ||
| 1750 | (funcall (if other 'switch-to-buffer-other-window 'switch-to-buffer) | ||
| 1751 | buf) | ||
| 1752 | (when (and cvs-find-file-and-jump (cvs-applicable-p fi 'diff-base)) | ||
| 1753 | (goto-line (cvs-find-modif fi))) | ||
| 1754 | buf)))))) | ||
| 1755 | |||
| 1756 | |||
| 1757 | (defun-cvs-mode (cvs-mode-undo . SIMPLE) (flags) | ||
| 1758 | "Undo local changes to all marked files. | ||
| 1759 | The file is removed and `cvs update FILE' is run." | ||
| 1760 | ;;"With prefix argument, prompt for cvs FLAGS." | ||
| 1761 | (interactive (list nil));; (cvs-flags-query 'cvs-undo-flags "undo flags") | ||
| 1762 | (if current-prefix-arg (call-interactively 'cvs-mode-revert-to-rev) | ||
| 1763 | (let* ((fis (cvs-do-removal 'undo "update" 'all)) | ||
| 1764 | (removedp (lambda (fi) (eq (cvs-fileinfo->type fi) 'REMOVED))) | ||
| 1765 | (fis-split (cvs-partition removedp fis)) | ||
| 1766 | (fis-removed (car fis-split)) | ||
| 1767 | (fis-other (cdr fis-split))) | ||
| 1768 | (if (null fis-other) | ||
| 1769 | (when fis-removed (cvs-mode-run "add" nil fis-removed)) | ||
| 1770 | (cvs-mode-run "update" flags fis-other | ||
| 1771 | :postproc | ||
| 1772 | (when fis-removed | ||
| 1773 | `((with-current-buffer ,(current-buffer) | ||
| 1774 | (cvs-mode-run "add" nil ',fis-removed))))))))) | ||
| 1775 | |||
| 1776 | |||
| 1777 | (defun-cvs-mode (cvs-mode-revert-to-rev . NOARGS) (rev) | ||
| 1778 | "Revert the selected files to an old revision." | ||
| 1779 | (interactive | ||
| 1780 | (list (or (cvs-prefix-get 'cvs-branch-prefix) | ||
| 1781 | (let ((current-prefix-arg '(4))) | ||
| 1782 | (cvs-flags-query 'cvs-idiff-version))))) | ||
| 1783 | (let* ((fis (cvs-mode-marked 'revert "revert" :file t)) | ||
| 1784 | (tag (concat "tmp_pcl_tag_" (make-temp-name ""))) | ||
| 1785 | (untag `((with-current-buffer ,(current-buffer) | ||
| 1786 | (cvs-mode-run "tag" (list "-d" ',tag) ',fis)))) | ||
| 1787 | (update `((with-current-buffer ,(current-buffer) | ||
| 1788 | (cvs-mode-run "update" (list "-j" ',tag "-j" ',rev) ',fis | ||
| 1789 | :postproc ',untag))))) | ||
| 1790 | (cvs-mode-run "tag" (list tag) fis :postproc update))) | ||
| 1791 | |||
| 1792 | |||
| 1793 | (defun-cvs-mode cvs-mode-delete-lock () | ||
| 1794 | "Delete the lock file that CVS is waiting for. | ||
| 1795 | Note that this can be dangerous. You should only do this | ||
| 1796 | if you are convinced that the process that created the lock is dead." | ||
| 1797 | (interactive) | ||
| 1798 | (let* ((default-directory (cvs-expand-dir-name cvs-lock-file)) | ||
| 1799 | (locks (directory-files default-directory nil cvs-lock-file-regexp))) | ||
| 1800 | (cond | ||
| 1801 | ((not locks) (error "No lock files found.")) | ||
| 1802 | ((yes-or-no-p (concat "Really delete locks in " cvs-lock-file "? ")) | ||
| 1803 | (dolist (lock locks) | ||
| 1804 | (cond ((file-directory-p lock) (delete-directory lock)) | ||
| 1805 | ((file-exists-p lock) (delete-file lock)))))))) | ||
| 1806 | |||
| 1807 | |||
| 1808 | (defun-cvs-mode cvs-mode-remove-handled () | ||
| 1809 | "Remove all lines that are handled. | ||
| 1810 | Empty directories are removed." | ||
| 1811 | (interactive) | ||
| 1812 | (cvs-cleanup-collection cvs-cookies | ||
| 1813 | t (or cvs-auto-remove-directories 'handled) t)) | ||
| 1814 | |||
| 1815 | |||
| 1816 | (defun-cvs-mode cvs-mode-acknowledge () | ||
| 1817 | "Remove all marked files from the buffer." | ||
| 1818 | (interactive) | ||
| 1819 | (dolist (fi (cvs-get-marked (cvs-ignore-marks-p "acknowledge") t)) | ||
| 1820 | (setf (cvs-fileinfo->type fi) 'DEAD)) | ||
| 1821 | (cvs-cleanup-collection cvs-cookies nil nil nil)) | ||
| 1822 | |||
| 1823 | ;;---------- | ||
| 1824 | (defun cvs-insert-full-path (tin) | ||
| 1825 | "Insert full path to the file described in TIN in the current buffer." | ||
| 1826 | (insert (format "%s\n" (cvs-full-path tin)))) | ||
| 1827 | |||
| 1828 | (defun cvs-do-removal (filter &optional cmd all) | ||
| 1829 | "Remove files. | ||
| 1830 | Returns a list of FIS that should be `cvs remove'd." | ||
| 1831 | (let* ((files (cvs-mode-marked filter cmd :file t :read-only t)) | ||
| 1832 | (fis (delete-if (lambda (fi) (eq (cvs-fileinfo->type fi) 'UNKNOWN)) | ||
| 1833 | (cvs-mode-marked filter cmd))) | ||
| 1834 | (silent (or (not cvs-confirm-removals) | ||
| 1835 | (cvs-every (lambda (fi) | ||
| 1836 | (or (not (file-exists-p | ||
| 1837 | (cvs-fileinfo->full-path fi))) | ||
| 1838 | (cvs-applicable-p fi 'safe-rm))) | ||
| 1839 | files)))) | ||
| 1840 | (when (and (not silent) (equal cvs-confirm-removals 'list)) | ||
| 1841 | (save-excursion | ||
| 1842 | (pop-to-buffer (cvs-temp-buffer)) | ||
| 1843 | (dolist (fi fis) | ||
| 1844 | (insert (cvs-fileinfo->full-path fi) "\n")))) | ||
| 1845 | (if (not (or silent | ||
| 1846 | (yes-or-no-p (format "Delete %d files? " (length files))))) | ||
| 1847 | (progn (message "Aborting") nil) | ||
| 1848 | (dolist (fi files) | ||
| 1849 | (let* ((type (cvs-fileinfo->type fi)) | ||
| 1850 | (file (cvs-fileinfo->full-path fi))) | ||
| 1851 | (when (or all (eq type 'UNKNOWN)) | ||
| 1852 | (when (file-exists-p file) (delete-file file)) | ||
| 1853 | (unless all (setf (cvs-fileinfo->type fi) 'DEAD) t)))) | ||
| 1854 | fis))) | ||
| 1855 | |||
| 1856 | (defun-cvs-mode (cvs-mode-remove . SIMPLE) (flags) | ||
| 1857 | "Remove all marked files. | ||
| 1858 | With prefix argument, prompt for cvs flags." | ||
| 1859 | (interactive (list (cvs-flags-query 'cvs-remove-flags "cvs remove flags"))) | ||
| 1860 | (let ((fis (cvs-do-removal 'remove))) | ||
| 1861 | (if fis (cvs-mode-run "remove" (cons "-f" flags) fis) | ||
| 1862 | (cvs-cleanup-collection cvs-cookies nil nil nil)))) | ||
| 1863 | |||
| 1864 | |||
| 1865 | (defvar cvs-tag-name "") | ||
| 1866 | (defun-cvs-mode (cvs-mode-tag . SIMPLE) (tag &optional flags) | ||
| 1867 | "Run `cvs tag TAG' on all selected files. | ||
| 1868 | With prefix argument, prompt for cvs flags." | ||
| 1869 | (interactive | ||
| 1870 | (list (setq cvs-tag-name | ||
| 1871 | (cvs-query-read cvs-tag-name "Tag name: " cvs-qtypedesc-tag)) | ||
| 1872 | (cvs-flags-query 'cvs-tag-flags "tag flags"))) | ||
| 1873 | (cvs-mode-do "tag" (append flags (list tag)) | ||
| 1874 | (when cvs-force-dir-tag 'tag))) | ||
| 1875 | |||
| 1876 | (defun-cvs-mode (cvs-mode-untag . SIMPLE) (tag &optional flags) | ||
| 1877 | "Run `cvs tag -d TAG' on all selected files. | ||
| 1878 | With prefix argument, prompt for cvs flags." | ||
| 1879 | (interactive | ||
| 1880 | (list (setq cvs-tag-name | ||
| 1881 | (cvs-query-read cvs-tag-name "Tag to delete: " cvs-qtypedesc-tag)) | ||
| 1882 | (cvs-flags-query 'cvs-tag-flags "tag flags"))) | ||
| 1883 | (cvs-mode-do "tag" (append '("-d") flags (list tag)) | ||
| 1884 | (when cvs-force-dir-tag 'tag))) | ||
| 1885 | |||
| 1886 | |||
| 1887 | ;; Byte compile files. | ||
| 1888 | |||
| 1889 | (defun-cvs-mode cvs-mode-byte-compile-files () | ||
| 1890 | "Run byte-compile-file on all selected files that end in '.el'." | ||
| 1891 | (interactive) | ||
| 1892 | (let ((marked (cvs-get-marked (cvs-ignore-marks-p "byte-compile")))) | ||
| 1893 | (dolist (fi marked) | ||
| 1894 | (let ((filename (cvs-fileinfo->full-path fi))) | ||
| 1895 | (when (string-match "\\.el\\'" filename) | ||
| 1896 | (byte-compile-file filename)))))) | ||
| 1897 | |||
| 1898 | ;; ChangeLog support. | ||
| 1899 | |||
| 1900 | ;;---------- | ||
| 1901 | (defun-cvs-mode cvs-mode-add-change-log-entry-other-window () | ||
| 1902 | "Add a ChangeLog entry in the ChangeLog of the current directory." | ||
| 1903 | (interactive) | ||
| 1904 | (let* ((fi (cvs-mode-marked nil nil :one t)) | ||
| 1905 | (default-directory (cvs-expand-dir-name (cvs-fileinfo->dir fi))) | ||
| 1906 | (buffer-file-name (expand-file-name (cvs-fileinfo->file fi)))) | ||
| 1907 | ;; This `save-excursion' is necessary because of interaction between | ||
| 1908 | ;; dynamic scoping and buffer-local variables: | ||
| 1909 | ;; the above binding of `buffer-file-name' has temporarily changed the | ||
| 1910 | ;; buffer-local variable (same thing for `default-directory'), so we | ||
| 1911 | ;; need to switch back to the original buffer before the unbinding | ||
| 1912 | ;; restores the old value. | ||
| 1913 | (save-excursion (add-change-log-entry-other-window)))) | ||
| 1914 | |||
| 1915 | ;; interactive commands to set optional flags | ||
| 1916 | |||
| 1917 | (defun cvs-mode-set-flags (flag) | ||
| 1918 | "Ask for new setting of cvs-FLAG-flags." | ||
| 1919 | (interactive | ||
| 1920 | (list (completing-read | ||
| 1921 | "Which flag: " | ||
| 1922 | (mapcar 'list '("cvs" "diff" "update" "status" "log" "tag" ;"rtag" | ||
| 1923 | "commit" "remove" "undo" "checkout")) | ||
| 1924 | nil t))) | ||
| 1925 | (let* ((sym (intern (concat "cvs-" flag "-flags")))) | ||
| 1926 | (let ((current-prefix-arg '(16))) | ||
| 1927 | (cvs-flags-query sym (concat flag " flags"))))) | ||
| 1928 | |||
| 1929 | |||
| 1930 | ;;;; | ||
| 1931 | ;;;; Utilities for the *cvs* buffer | ||
| 1932 | ;;;; | ||
| 1933 | |||
| 1934 | ;;---------- | ||
| 1935 | (defun cvs-full-path (tin) | ||
| 1936 | "Return the full path for the file that is described in TIN." | ||
| 1937 | (cvs-fileinfo->full-path (ewoc-data tin))) | ||
| 1938 | |||
| 1939 | ;;---------- | ||
| 1940 | (defun cvs-dir-member-p (fileinfo dir) | ||
| 1941 | "Return true if FILEINFO represents a file in directory DIR." | ||
| 1942 | (and (not (eq (cvs-fileinfo->type fileinfo) 'DIRCHANGE)) | ||
| 1943 | (cvs-string-prefix-p dir (cvs-fileinfo->dir fileinfo)) | ||
| 1944 | (not (memq (cvs-fileinfo->subtype fileinfo) '(HEADER FOOTER))))) | ||
| 1945 | |||
| 1946 | (defun cvs-execute-single-file (fi extractor program constant-args) | ||
| 1947 | "Internal function for `cvs-execute-single-file-list'." | ||
| 1948 | (let* ((cur-dir (cvs-fileinfo->dir fi)) | ||
| 1949 | (default-directory (cvs-expand-dir-name cur-dir)) | ||
| 1950 | (inhibit-read-only t) | ||
| 1951 | (arg-list (funcall extractor fi))) | ||
| 1952 | |||
| 1953 | ;; Execute the command unless extractor returned t. | ||
| 1954 | (when (listp arg-list) | ||
| 1955 | (let* ((args (append constant-args arg-list))) | ||
| 1956 | |||
| 1957 | (insert (format "=== cd %s\n=== %s %s\n\n" | ||
| 1958 | cur-dir program (cvs-strings->string args))) | ||
| 1959 | |||
| 1960 | ;; FIXME: return the exit status? | ||
| 1961 | (apply 'call-process program nil t t args) | ||
| 1962 | (goto-char (point-max)))))) | ||
| 1963 | |||
| 1964 | ;; FIXME: make this run in the background ala cvs-run-process... | ||
| 1965 | (defun cvs-execute-single-file-list (fis extractor program constant-args) | ||
| 1966 | "Run PROGRAM on all elements on FIS. | ||
| 1967 | The PROGRAM will be called with pwd set to the directory the files | ||
| 1968 | reside in. CONSTANT-ARGS is a list of strings to pass as arguments to | ||
| 1969 | PROGRAM. The arguments given to the program will be CONSTANT-ARGS | ||
| 1970 | followed by the list that EXTRACTOR returns. | ||
| 1971 | |||
| 1972 | EXTRACTOR will be called once for each file on FIS. It is given | ||
| 1973 | one argument, the cvs-fileinfo. It can return t, which means ignore | ||
| 1974 | this file, or a list of arguments to send to the program." | ||
| 1975 | (dolist (fi fis) | ||
| 1976 | (cvs-execute-single-file fi extractor program constant-args))) | ||
| 1977 | |||
| 1978 | |||
| 1979 | (defun cvs-revert-if-needed (fis) | ||
| 1980 | (dolist (fileinfo fis) | ||
| 1981 | (let* ((file (cvs-fileinfo->full-path fileinfo)) | ||
| 1982 | (buffer (find-buffer-visiting file))) | ||
| 1983 | ;; For a revert to happen the user must be editing the file... | ||
| 1984 | (unless (or (null buffer) | ||
| 1985 | (eq (cvs-fileinfo->type fileinfo) 'MESSAGE) | ||
| 1986 | ;; FIXME: check whether revert is really needed. | ||
| 1987 | ;; `(verify-visited-file-modtime buffer)' doesn't cut it | ||
| 1988 | ;; because it only looks at the time stamp (it ignores | ||
| 1989 | ;; read-write changes) which is not changed by `commit'. | ||
| 1990 | (buffer-modified-p buffer)) | ||
| 1991 | (with-current-buffer buffer | ||
| 1992 | (let ((cvs-buf-was-ro buffer-read-only)) | ||
| 1993 | (ignore-errors | ||
| 1994 | ;; Ideally, we'd like to prevent changing the (minor) modes. | ||
| 1995 | ;; But we do want to reset the mode for some cases, most notably | ||
| 1996 | ;; VC. Maybe it'd better to reset VC explicitely ? | ||
| 1997 | (revert-buffer 'ignore-auto 'dont-ask)) ; 'preserve-modes | ||
| 1998 | ;; protect the buffer-read-only setting | ||
| 1999 | (if cvs-buf-was-ro (toggle-read-only 1)))))))) | ||
| 2000 | |||
| 2001 | |||
| 2002 | |||
| 2003 | (defun cvs-change-cvsroot (newroot) | ||
| 2004 | "Change the cvsroot." | ||
| 2005 | (interactive "DNew repository: ") | ||
| 2006 | (if (or (file-directory-p (expand-file-name "CVSROOT" newroot)) | ||
| 2007 | (y-or-n-p (concat "Warning: no CVSROOT found inside repository." | ||
| 2008 | " Change cvs-cvsroot anyhow?"))) | ||
| 2009 | (setq cvs-cvsroot newroot))) | ||
| 2010 | |||
| 2011 | ;;;; | ||
| 2012 | ;;;; useful global settings | ||
| 2013 | ;;;; | ||
| 2014 | |||
| 2015 | ;;;###autoload | ||
| 2016 | (add-to-list 'completion-ignored-extensions "CVS/") | ||
| 2017 | |||
| 2018 | ;; | ||
| 2019 | ;; Hook to allow calling PCL-CVS by visiting the /CVS subdirectory | ||
| 2020 | ;; | ||
| 2021 | |||
| 2022 | ;;;###autoload | ||
| 2023 | (defcustom cvs-dired-use-hook '(4) | ||
| 2024 | "Whether or not opening a CVS directory should run PCL-CVS. | ||
| 2025 | NIL means never do it. | ||
| 2026 | ALWAYS means to always do it unless a prefix argument is given to the | ||
| 2027 | command that prompted the opening of the directory. | ||
| 2028 | Anything else means to do it only if the prefix arg is equal to this value." | ||
| 2029 | :group 'pcl-cvs | ||
| 2030 | :type '(choice (const :tag "Never" nil) | ||
| 2031 | (const :tag "Always" always) | ||
| 2032 | (const :tag "Prefix" (4)))) | ||
| 2033 | |||
| 2034 | ;;;###autoload | ||
| 2035 | (progn | ||
| 2036 | (defun cvs-dired-noselect (dir) | ||
| 2037 | "Run `cvs-examine' if DIR is a CVS administrative directory. | ||
| 2038 | The exact behavior is determined also by `cvs-dired-use-hook'." | ||
| 2039 | (when (stringp dir) | ||
| 2040 | (setq dir (directory-file-name dir)) | ||
| 2041 | (when (and (string= "CVS" (file-name-nondirectory dir)) | ||
| 2042 | (file-readable-p (expand-file-name "Entries" dir)) | ||
| 2043 | cvs-dired-use-hook | ||
| 2044 | (if (eq cvs-dired-use-hook 'always) | ||
| 2045 | (not current-prefix-arg) | ||
| 2046 | (equal current-prefix-arg cvs-dired-use-hook))) | ||
| 2047 | (save-excursion | ||
| 2048 | (cvs-examine (file-name-directory dir) t t)))))) | ||
| 2049 | |||
| 2050 | ;; | ||
| 2051 | ;; hook into VC | ||
| 2052 | ;; | ||
| 2053 | |||
| 2054 | (defadvice vc-simple-command (after pcl-cvs-vc activate) | ||
| 2055 | (cvs-vc-command-advice "*vc-info*" (ad-get-arg 1) (ad-get-arg 3))) | ||
| 2056 | |||
| 2057 | (defadvice vc-do-command (after pcl-cvs-vc activate) | ||
| 2058 | (cvs-vc-command-advice (or (ad-get-arg 0) "*vc*") | ||
| 2059 | (ad-get-arg 2) (ad-get-arg 5))) | ||
| 2060 | |||
| 2061 | (defun cvs-vc-command-advice (buffer command cvscmd) | ||
| 2062 | (when (and (setq buffer (get-buffer buffer)) | ||
| 2063 | (equal command "cvs") | ||
| 2064 | ;; don't parse output we don't understand. | ||
| 2065 | (member cvscmd cvs-parse-known-commands)) | ||
| 2066 | (save-excursion | ||
| 2067 | (let ((dir (with-current-buffer buffer default-directory)) | ||
| 2068 | (cvs-from-vc t)) | ||
| 2069 | (dolist (cvs-buf (buffer-list)) | ||
| 2070 | (set-buffer cvs-buf) | ||
| 2071 | ;; look for a corresponding pcl-cvs buffer | ||
| 2072 | (when (and (eq major-mode 'cvs-mode) | ||
| 2073 | (cvs-string-prefix-p default-directory dir)) | ||
| 2074 | (let ((subdir (substring dir (length default-directory)))) | ||
| 2075 | (set-buffer buffer) | ||
| 2076 | (set (make-local-variable 'cvs-buffer) cvs-buf) | ||
| 2077 | ;; VC never (?) does `cvs -n update' so dcd=nil | ||
| 2078 | ;; should probably always be the right choice. | ||
| 2079 | (cvs-parse-process nil subdir)))))))) | ||
| 2080 | |||
| 2081 | ;; | ||
| 2082 | ;; Hook into write-buffer | ||
| 2083 | ;; | ||
| 2084 | |||
| 2085 | (defun cvs-mark-buffer-changed () | ||
| 2086 | (let* ((file (expand-file-name buffer-file-name)) | ||
| 2087 | (version (and (fboundp 'vc-backend) | ||
| 2088 | (eq (vc-backend file) 'CVS) | ||
| 2089 | (vc-workfile-version file)))) | ||
| 2090 | (when version | ||
| 2091 | (save-excursion | ||
| 2092 | (dolist (cvs-buf (buffer-list)) | ||
| 2093 | (set-buffer cvs-buf) | ||
| 2094 | ;; look for a corresponding pcl-cvs buffer | ||
| 2095 | (when (and (eq major-mode 'cvs-mode) | ||
| 2096 | (cvs-string-prefix-p default-directory file)) | ||
| 2097 | (let* ((file (substring file (length default-directory))) | ||
| 2098 | (fi (cvs-create-fileinfo | ||
| 2099 | (if (string= "0" version) | ||
| 2100 | 'ADDED 'MODIFIED) | ||
| 2101 | (or (file-name-directory file) "") | ||
| 2102 | (file-name-nondirectory file) | ||
| 2103 | "cvs-mark-buffer-changed"))) | ||
| 2104 | (cvs-addto-collection cvs-cookies fi)))))))) | ||
| 2105 | |||
| 2106 | (add-hook 'after-save-hook 'cvs-mark-buffer-changed) | ||
| 2107 | |||
| 2108 | ;; | ||
| 2109 | ;; hook into uniquify | ||
| 2110 | ;; | ||
| 2111 | |||
| 2112 | (defadvice uniquify-buffer-file-name (after pcl-cvs-uniquify activate) | ||
| 2113 | (or ad-return-value | ||
| 2114 | (save-excursion | ||
| 2115 | (set-buffer (ad-get-arg 0)) | ||
| 2116 | (when (eq major-mode 'cvs-mode) | ||
| 2117 | (setq ad-return-value list-buffers-directory))))) | ||
| 2118 | |||
| 2119 | |||
| 2120 | (provide 'pcvs) | ||
| 2121 | |||
| 2122 | ;;; pcvs.el ends here | ||