diff options
| author | Eric S. Raymond | 2008-05-03 09:28:04 +0000 |
|---|---|---|
| committer | Eric S. Raymond | 2008-05-03 09:28:04 +0000 |
| commit | b1ddeeb7e718106117aa4c28fe8f152858e15af0 (patch) | |
| tree | 3d412e494369fe024099969aa2ac28dc82481b42 | |
| parent | 7265c6e8a87c1a112b16384a7d3f62de869c2589 (diff) | |
| download | emacs-b1ddeeb7e718106117aa4c28fe8f152858e15af0.tar.gz emacs-b1ddeeb7e718106117aa4c28fe8f152858e15af0.zip | |
Partially undo the dispatcher split, it needs to happen more gradually
to avoid breaking things.
| -rw-r--r-- | lisp/vc-dispatcher.el | 1402 | ||||
| -rw-r--r-- | lisp/vc.el | 1392 |
2 files changed, 1393 insertions, 1401 deletions
diff --git a/lisp/vc-dispatcher.el b/lisp/vc-dispatcher.el index 765b8f58e2b..6afe2eeb8e5 100644 --- a/lisp/vc-dispatcher.el +++ b/lisp/vc-dispatcher.el | |||
| @@ -76,52 +76,6 @@ | |||
| 76 | 76 | ||
| 77 | (provide 'vc-dispatcher) | 77 | (provide 'vc-dispatcher) |
| 78 | 78 | ||
| 79 | (eval-when-compile | ||
| 80 | (require 'cl) | ||
| 81 | (require 'dired) ; for dired-map-over-marks macro | ||
| 82 | (require 'dired-aux)) ; for dired-kill-{line,tree} | ||
| 83 | |||
| 84 | (defcustom vc-delete-logbuf-window t | ||
| 85 | "If non-nil, delete the *VC-log* buffer and window after each logical action. | ||
| 86 | If nil, bury that buffer instead. | ||
| 87 | This is most useful if you have multiple windows on a frame and would like to | ||
| 88 | preserve the setting." | ||
| 89 | :type 'boolean | ||
| 90 | :group 'vc) | ||
| 91 | |||
| 92 | (defcustom vc-command-messages nil | ||
| 93 | "If non-nil, display run messages from back-end commands." | ||
| 94 | :type 'boolean | ||
| 95 | :group 'vc) | ||
| 96 | |||
| 97 | (defcustom vc-dired-listing-switches "-al" | ||
| 98 | "Switches passed to `ls' for vc-dired. MUST contain the `l' option." | ||
| 99 | :type 'string | ||
| 100 | :group 'vc | ||
| 101 | :version "21.1") | ||
| 102 | |||
| 103 | (defcustom vc-dired-recurse t | ||
| 104 | "If non-nil, show directory trees recursively in VC Dired." | ||
| 105 | :type 'boolean | ||
| 106 | :group 'vc | ||
| 107 | :version "20.3") | ||
| 108 | |||
| 109 | (defcustom vc-dired-terse-display t | ||
| 110 | "If non-nil, show only locked or locally modified files in VC Dired." | ||
| 111 | :type 'boolean | ||
| 112 | :group 'vc | ||
| 113 | :version "20.3") | ||
| 114 | |||
| 115 | (defcustom vc-dir-mode-hook nil | ||
| 116 | "Normal hook run by `vc-dir-mode'. | ||
| 117 | See `run-hooks'." | ||
| 118 | :type 'hook | ||
| 119 | :group 'vc) | ||
| 120 | |||
| 121 | (defvar vc-log-fileset) | ||
| 122 | (defvar vc-dired-mode nil) | ||
| 123 | (make-variable-buffer-local 'vc-dired-mode) | ||
| 124 | |||
| 125 | ;; Common command execution logic | 79 | ;; Common command execution logic |
| 126 | 80 | ||
| 127 | (defun vc-process-filter (p s) | 81 | (defun vc-process-filter (p s) |
| @@ -273,7 +227,7 @@ that is inserted into the command line before the filename." | |||
| 273 | (and (stringp buffer) | 227 | (and (stringp buffer) |
| 274 | (string= (buffer-name) buffer)) | 228 | (string= (buffer-name) buffer)) |
| 275 | (eq buffer (current-buffer))) | 229 | (eq buffer (current-buffer))) |
| 276 | (vc-setup-buffer buffer)) | 230 | (vc-setup-buffer (or buffer "*vc*"))) |
| 277 | ;; If there's some previous async process still running, just kill it. | 231 | ;; If there's some previous async process still running, just kill it. |
| 278 | (let ((oldproc (get-buffer-process (current-buffer)))) | 232 | (let ((oldproc (get-buffer-process (current-buffer)))) |
| 279 | ;; If we wanted to wait for oldproc to finish before doing | 233 | ;; If we wanted to wait for oldproc to finish before doing |
| @@ -333,1356 +287,4 @@ that is inserted into the command line before the filename." | |||
| 333 | ',command ',file-or-list ',flags)) | 287 | ',command ',file-or-list ',flags)) |
| 334 | status)))) | 288 | status)))) |
| 335 | 289 | ||
| 336 | ;; Context management | 290 | ;;; vc-dispatcher.el ends here |
| 337 | |||
| 338 | (defun vc-position-context (posn) | ||
| 339 | "Save a bit of the text around POSN in the current buffer. | ||
| 340 | Used to help us find the corresponding position again later | ||
| 341 | if markers are destroyed or corrupted." | ||
| 342 | ;; A lot of this was shamelessly lifted from Sebastian Kremer's | ||
| 343 | ;; rcs.el mode. | ||
| 344 | (list posn | ||
| 345 | (buffer-size) | ||
| 346 | (buffer-substring posn | ||
| 347 | (min (point-max) (+ posn 100))))) | ||
| 348 | |||
| 349 | (defun vc-find-position-by-context (context) | ||
| 350 | "Return the position of CONTEXT in the current buffer. | ||
| 351 | If CONTEXT cannot be found, return nil." | ||
| 352 | (let ((context-string (nth 2 context))) | ||
| 353 | (if (equal "" context-string) | ||
| 354 | (point-max) | ||
| 355 | (save-excursion | ||
| 356 | (let ((diff (- (nth 1 context) (buffer-size)))) | ||
| 357 | (when (< diff 0) (setq diff (- diff))) | ||
| 358 | (goto-char (nth 0 context)) | ||
| 359 | (if (or (search-forward context-string nil t) | ||
| 360 | ;; Can't use search-backward since the match may continue | ||
| 361 | ;; after point. | ||
| 362 | (progn (goto-char (- (point) diff (length context-string))) | ||
| 363 | ;; goto-char doesn't signal an error at | ||
| 364 | ;; beginning of buffer like backward-char would | ||
| 365 | (search-forward context-string nil t))) | ||
| 366 | ;; to beginning of OSTRING | ||
| 367 | (- (point) (length context-string)))))))) | ||
| 368 | |||
| 369 | (defun vc-context-matches-p (posn context) | ||
| 370 | "Return t if POSN matches CONTEXT, nil otherwise." | ||
| 371 | (let* ((context-string (nth 2 context)) | ||
| 372 | (len (length context-string)) | ||
| 373 | (end (+ posn len))) | ||
| 374 | (if (> end (1+ (buffer-size))) | ||
| 375 | nil | ||
| 376 | (string= context-string (buffer-substring posn end))))) | ||
| 377 | |||
| 378 | (defun vc-buffer-context () | ||
| 379 | "Return a list (POINT-CONTEXT MARK-CONTEXT REPARSE). | ||
| 380 | Used by `vc-restore-buffer-context' to later restore the context." | ||
| 381 | (let ((point-context (vc-position-context (point))) | ||
| 382 | ;; Use mark-marker to avoid confusion in transient-mark-mode. | ||
| 383 | (mark-context (when (eq (marker-buffer (mark-marker)) (current-buffer)) | ||
| 384 | (vc-position-context (mark-marker)))) | ||
| 385 | ;; Make the right thing happen in transient-mark-mode. | ||
| 386 | (mark-active nil) | ||
| 387 | ;; The new compilation code does not use compilation-error-list any | ||
| 388 | ;; more, so the code below is now ineffective and might as well | ||
| 389 | ;; be disabled. -- Stef | ||
| 390 | ;; ;; We may want to reparse the compilation buffer after revert | ||
| 391 | ;; (reparse (and (boundp 'compilation-error-list) ;compile loaded | ||
| 392 | ;; ;; Construct a list; each elt is nil or a buffer | ||
| 393 | ;; ;; if that buffer is a compilation output buffer | ||
| 394 | ;; ;; that contains markers into the current buffer. | ||
| 395 | ;; (save-current-buffer | ||
| 396 | ;; (mapcar (lambda (buffer) | ||
| 397 | ;; (set-buffer buffer) | ||
| 398 | ;; (let ((errors (or | ||
| 399 | ;; compilation-old-error-list | ||
| 400 | ;; compilation-error-list)) | ||
| 401 | ;; (buffer-error-marked-p nil)) | ||
| 402 | ;; (while (and (consp errors) | ||
| 403 | ;; (not buffer-error-marked-p)) | ||
| 404 | ;; (and (markerp (cdr (car errors))) | ||
| 405 | ;; (eq buffer | ||
| 406 | ;; (marker-buffer | ||
| 407 | ;; (cdr (car errors)))) | ||
| 408 | ;; (setq buffer-error-marked-p t)) | ||
| 409 | ;; (setq errors (cdr errors))) | ||
| 410 | ;; (if buffer-error-marked-p buffer))) | ||
| 411 | ;; (buffer-list))))) | ||
| 412 | (reparse nil)) | ||
| 413 | (list point-context mark-context reparse))) | ||
| 414 | |||
| 415 | (defun vc-restore-buffer-context (context) | ||
| 416 | "Restore point/mark, and reparse any affected compilation buffers. | ||
| 417 | CONTEXT is that which `vc-buffer-context' returns." | ||
| 418 | (let ((point-context (nth 0 context)) | ||
| 419 | (mark-context (nth 1 context)) | ||
| 420 | ;; (reparse (nth 2 context)) | ||
| 421 | ) | ||
| 422 | ;; The new compilation code does not use compilation-error-list any | ||
| 423 | ;; more, so the code below is now ineffective and might as well | ||
| 424 | ;; be disabled. -- Stef | ||
| 425 | ;; ;; Reparse affected compilation buffers. | ||
| 426 | ;; (while reparse | ||
| 427 | ;; (if (car reparse) | ||
| 428 | ;; (with-current-buffer (car reparse) | ||
| 429 | ;; (let ((compilation-last-buffer (current-buffer)) ;select buffer | ||
| 430 | ;; ;; Record the position in the compilation buffer of | ||
| 431 | ;; ;; the last error next-error went to. | ||
| 432 | ;; (error-pos (marker-position | ||
| 433 | ;; (car (car-safe compilation-error-list))))) | ||
| 434 | ;; ;; Reparse the error messages as far as they were parsed before. | ||
| 435 | ;; (compile-reinitialize-errors '(4) compilation-parsing-end) | ||
| 436 | ;; ;; Move the pointer up to find the error we were at before | ||
| 437 | ;; ;; reparsing. Now next-error should properly go to the next one. | ||
| 438 | ;; (while (and compilation-error-list | ||
| 439 | ;; (/= error-pos (car (car compilation-error-list)))) | ||
| 440 | ;; (setq compilation-error-list (cdr compilation-error-list)))))) | ||
| 441 | ;; (setq reparse (cdr reparse))) | ||
| 442 | |||
| 443 | ;; if necessary, restore point and mark | ||
| 444 | (if (not (vc-context-matches-p (point) point-context)) | ||
| 445 | (let ((new-point (vc-find-position-by-context point-context))) | ||
| 446 | (when new-point (goto-char new-point)))) | ||
| 447 | (and mark-active | ||
| 448 | mark-context | ||
| 449 | (not (vc-context-matches-p (mark) mark-context)) | ||
| 450 | (let ((new-mark (vc-find-position-by-context mark-context))) | ||
| 451 | (when new-mark (set-mark new-mark)))))) | ||
| 452 | |||
| 453 | (defvar vc-dired-window-configuration) | ||
| 454 | |||
| 455 | ;; Command closures | ||
| 456 | |||
| 457 | ;; FIXME: The rev argument is VCS-specific and needs to be factored out | ||
| 458 | (defun vc-start-entry (files rev comment initial-contents msg action &optional after-hook) | ||
| 459 | "Accept a comment for an operation on FILES revision REV. | ||
| 460 | If COMMENT is nil, pop up a VC-log buffer, emit MSG, and set the | ||
| 461 | action on close to ACTION. If COMMENT is a string and | ||
| 462 | INITIAL-CONTENTS is non-nil, then COMMENT is used as the initial | ||
| 463 | contents of the log entry buffer. If COMMENT is a string and | ||
| 464 | INITIAL-CONTENTS is nil, do action immediately as if the user had | ||
| 465 | entered COMMENT. If COMMENT is t, also do action immediately with an | ||
| 466 | empty comment. Remember the file's buffer in `vc-parent-buffer' | ||
| 467 | \(current one if no file). AFTER-HOOK specifies the local value | ||
| 468 | for `vc-log-after-operation-hook'." | ||
| 469 | (let ((parent | ||
| 470 | (if (or (eq major-mode 'vc-dired-mode) (eq major-mode 'vc-dir-mode)) | ||
| 471 | ;; If we are called from VC dired, the parent buffer is | ||
| 472 | ;; the current buffer. | ||
| 473 | (current-buffer) | ||
| 474 | (if (and files (equal (length files) 1)) | ||
| 475 | (get-file-buffer (car files)) | ||
| 476 | (current-buffer))))) | ||
| 477 | (when vc-before-checkin-hook | ||
| 478 | (if files | ||
| 479 | (with-current-buffer parent | ||
| 480 | (run-hooks 'vc-before-checkin-hook)) | ||
| 481 | (run-hooks 'vc-before-checkin-hook))) | ||
| 482 | (if (and comment (not initial-contents)) | ||
| 483 | (set-buffer (get-buffer-create "*VC-log*")) | ||
| 484 | (pop-to-buffer (get-buffer-create "*VC-log*"))) | ||
| 485 | (set (make-local-variable 'vc-parent-buffer) parent) | ||
| 486 | (set (make-local-variable 'vc-parent-buffer-name) | ||
| 487 | (concat " from " (buffer-name vc-parent-buffer))) | ||
| 488 | ;;(if file (vc-mode-line file)) | ||
| 489 | (vc-log-edit files) | ||
| 490 | (make-local-variable 'vc-log-after-operation-hook) | ||
| 491 | (when after-hook | ||
| 492 | (setq vc-log-after-operation-hook after-hook)) | ||
| 493 | (setq vc-log-operation action) | ||
| 494 | (setq vc-log-revision rev) | ||
| 495 | (when comment | ||
| 496 | (erase-buffer) | ||
| 497 | (when (stringp comment) (insert comment))) | ||
| 498 | (if (or (not comment) initial-contents) | ||
| 499 | (message "%s Type C-c C-c when done" msg) | ||
| 500 | (vc-finish-logentry (eq comment t))))) | ||
| 501 | |||
| 502 | (defun vc-finish-logentry (&optional nocomment) | ||
| 503 | "Complete the operation implied by the current log entry. | ||
| 504 | Use the contents of the current buffer as a check-in or registration | ||
| 505 | comment. If the optional arg NOCOMMENT is non-nil, then don't check | ||
| 506 | the buffer contents as a comment." | ||
| 507 | (interactive) | ||
| 508 | ;; Check and record the comment, if any. | ||
| 509 | (unless nocomment | ||
| 510 | (run-hooks 'vc-logentry-check-hook)) | ||
| 511 | ;; Sync parent buffer in case the user modified it while editing the comment. | ||
| 512 | ;; But not if it is a vc-dired buffer. | ||
| 513 | (with-current-buffer vc-parent-buffer | ||
| 514 | (or vc-dired-mode (eq major-mode 'vc-dir-mode) (vc-buffer-sync))) | ||
| 515 | (unless vc-log-operation | ||
| 516 | (error "No log operation is pending")) | ||
| 517 | ;; save the parameters held in buffer-local variables | ||
| 518 | (let ((log-operation vc-log-operation) | ||
| 519 | (log-fileset vc-log-fileset) | ||
| 520 | (log-revision vc-log-revision) | ||
| 521 | (log-entry (buffer-string)) | ||
| 522 | (after-hook vc-log-after-operation-hook) | ||
| 523 | (tmp-vc-parent-buffer vc-parent-buffer)) | ||
| 524 | (pop-to-buffer vc-parent-buffer) | ||
| 525 | ;; OK, do it to it | ||
| 526 | (save-excursion | ||
| 527 | (funcall log-operation | ||
| 528 | log-fileset | ||
| 529 | log-revision | ||
| 530 | log-entry)) | ||
| 531 | ;; Remove checkin window (after the checkin so that if that fails | ||
| 532 | ;; we don't zap the *VC-log* buffer and the typing therein). | ||
| 533 | ;; -- IMO this should be replaced with quit-window | ||
| 534 | (let ((logbuf (get-buffer "*VC-log*"))) | ||
| 535 | (cond ((and logbuf vc-delete-logbuf-window) | ||
| 536 | (delete-windows-on logbuf (selected-frame)) | ||
| 537 | ;; Kill buffer and delete any other dedicated windows/frames. | ||
| 538 | (kill-buffer logbuf)) | ||
| 539 | (logbuf (pop-to-buffer "*VC-log*") | ||
| 540 | (bury-buffer) | ||
| 541 | (pop-to-buffer tmp-vc-parent-buffer)))) | ||
| 542 | ;; Now make sure we see the expanded headers | ||
| 543 | (when log-fileset | ||
| 544 | (mapc | ||
| 545 | (lambda (file) (vc-resynch-buffer file vc-keep-workfiles t)) | ||
| 546 | log-fileset)) | ||
| 547 | (when vc-dired-mode | ||
| 548 | (dired-move-to-filename)) | ||
| 549 | (when (eq major-mode 'vc-dir-mode) | ||
| 550 | (vc-dir-move-to-goal-column)) | ||
| 551 | (run-hooks after-hook 'vc-finish-logentry-hook))) | ||
| 552 | |||
| 553 | ;; The VC directory major mode. Coopt Dired for this. | ||
| 554 | ;; All VC commands get mapped into logical equivalents. | ||
| 555 | |||
| 556 | (defvar vc-dired-switches) | ||
| 557 | (defvar vc-dired-terse-mode) | ||
| 558 | |||
| 559 | (defvar vc-dired-mode-map | ||
| 560 | (let ((map (make-sparse-keymap)) | ||
| 561 | (vmap (make-sparse-keymap))) | ||
| 562 | (define-key map "\C-xv" vmap) | ||
| 563 | (define-key map "v" vmap) | ||
| 564 | (set-keymap-parent vmap vc-prefix-map) | ||
| 565 | (define-key vmap "t" 'vc-dired-toggle-terse-mode) | ||
| 566 | map)) | ||
| 567 | |||
| 568 | (define-derived-mode vc-dired-mode dired-mode "Dired under " | ||
| 569 | "The major mode used in VC directory buffers. | ||
| 570 | |||
| 571 | It works like Dired, but lists only files under version control, with | ||
| 572 | the current VC state of each file being indicated in the place of the | ||
| 573 | file's link count, owner, group and size. Subdirectories are also | ||
| 574 | listed, and you may insert them into the buffer as desired, like in | ||
| 575 | Dired. | ||
| 576 | |||
| 577 | All Dired commands operate normally, with the exception of `v', which | ||
| 578 | is redefined as the version control prefix, so that you can type | ||
| 579 | `vl', `v=' etc. to invoke `vc-print-log', `vc-diff', and the like on | ||
| 580 | the file named in the current Dired buffer line. `vv' invokes | ||
| 581 | `vc-next-action' on this file, or on all files currently marked. | ||
| 582 | There is a special command, `*l', to mark all files currently locked." | ||
| 583 | ;; define-derived-mode does it for us in Emacs-21, but not in Emacs-20. | ||
| 584 | ;; We do it here because dired might not be loaded yet | ||
| 585 | ;; when vc-dired-mode-map is initialized. | ||
| 586 | (set-keymap-parent vc-dired-mode-map dired-mode-map) | ||
| 587 | (add-hook 'dired-after-readin-hook 'vc-dired-hook nil t) | ||
| 588 | ;; The following is slightly modified from files.el, | ||
| 589 | ;; because file lines look a bit different in vc-dired-mode | ||
| 590 | ;; (the column before the date does not end in a digit). | ||
| 591 | ;; albinus: It should be done in the original declaration. Problem | ||
| 592 | ;; is the optional empty state-info; otherwise ")" would be good | ||
| 593 | ;; enough as delimeter. | ||
| 594 | (set (make-local-variable 'directory-listing-before-filename-regexp) | ||
| 595 | (let* ((l "\\([A-Za-z]\\|[^\0-\177]\\)") | ||
| 596 | ;; In some locales, month abbreviations are as short as 2 letters, | ||
| 597 | ;; and they can be followed by ".". | ||
| 598 | (month (concat l l "+\\.?")) | ||
| 599 | (s " ") | ||
| 600 | (yyyy "[0-9][0-9][0-9][0-9]") | ||
| 601 | (dd "[ 0-3][0-9]") | ||
| 602 | (HH:MM "[ 0-2][0-9]:[0-5][0-9]") | ||
| 603 | (seconds "[0-6][0-9]\\([.,][0-9]+\\)?") | ||
| 604 | (zone "[-+][0-2][0-9][0-5][0-9]") | ||
| 605 | (iso-mm-dd "[01][0-9]-[0-3][0-9]") | ||
| 606 | (iso-time (concat HH:MM "\\(:" seconds "\\( ?" zone "\\)?\\)?")) | ||
| 607 | (iso (concat "\\(\\(" yyyy "-\\)?" iso-mm-dd "[ T]" iso-time | ||
| 608 | "\\|" yyyy "-" iso-mm-dd "\\)")) | ||
| 609 | (western (concat "\\(" month s "+" dd "\\|" dd "\\.?" s month "\\)" | ||
| 610 | s "+" | ||
| 611 | "\\(" HH:MM "\\|" yyyy "\\)")) | ||
| 612 | (western-comma (concat month s "+" dd "," s "+" yyyy)) | ||
| 613 | ;; Japanese MS-Windows ls-lisp has one-digit months, and | ||
| 614 | ;; omits the Kanji characters after month and day-of-month. | ||
| 615 | (mm "[ 0-1]?[0-9]") | ||
| 616 | (japanese | ||
| 617 | (concat mm l "?" s dd l "?" s "+" | ||
| 618 | "\\(" HH:MM "\\|" yyyy l "?" "\\)"))) | ||
| 619 | ;; the .* below ensures that we find the last match on a line | ||
| 620 | (concat ".*" s | ||
| 621 | "\\(" western "\\|" western-comma "\\|" japanese "\\|" iso "\\)" | ||
| 622 | s "+"))) | ||
| 623 | (and (boundp 'vc-dired-switches) | ||
| 624 | vc-dired-switches | ||
| 625 | (set (make-local-variable 'dired-actual-switches) | ||
| 626 | vc-dired-switches)) | ||
| 627 | (set (make-local-variable 'vc-dired-terse-mode) vc-dired-terse-display) | ||
| 628 | ;; FIXME: This needs to be factored out | ||
| 629 | (let ((backend-name (symbol-name (vc-responsible-backend | ||
| 630 | default-directory)))) | ||
| 631 | (setq mode-name (concat mode-name backend-name)) | ||
| 632 | ;; Add menu after `vc-dired-mode-map' has `dired-mode-map' as the parent. | ||
| 633 | (let ((vc-dire-menu-map (copy-keymap vc-menu-map))) | ||
| 634 | (define-key-after (lookup-key vc-dired-mode-map [menu-bar]) [vc] | ||
| 635 | (cons backend-name vc-dire-menu-map) 'subdir))) | ||
| 636 | (setq vc-dired-mode t)) | ||
| 637 | |||
| 638 | (defun vc-dired-toggle-terse-mode () | ||
| 639 | "Toggle terse display in VC Dired." | ||
| 640 | (interactive) | ||
| 641 | (if (not vc-dired-mode) | ||
| 642 | nil | ||
| 643 | (setq vc-dired-terse-mode (not vc-dired-terse-mode)) | ||
| 644 | (if vc-dired-terse-mode | ||
| 645 | (vc-dired-hook) | ||
| 646 | (revert-buffer)))) | ||
| 647 | |||
| 648 | (defun vc-dired-mark-locked () | ||
| 649 | "Mark all files currently locked." | ||
| 650 | (interactive) | ||
| 651 | (dired-mark-if (let ((f (dired-get-filename nil t))) | ||
| 652 | (and f | ||
| 653 | (not (file-directory-p f)) | ||
| 654 | (not (vc-up-to-date-p f)))) | ||
| 655 | "locked file")) | ||
| 656 | |||
| 657 | (define-key vc-dired-mode-map "*l" 'vc-dired-mark-locked) | ||
| 658 | |||
| 659 | (defun vc-dired-reformat-line (vc-info) | ||
| 660 | "Reformat a directory-listing line. | ||
| 661 | Replace various columns with version control information, VC-INFO. | ||
| 662 | This code, like dired, assumes UNIX -l format." | ||
| 663 | (beginning-of-line) | ||
| 664 | (when (re-search-forward | ||
| 665 | ;; Match link count, owner, group, size. Group may be missing, | ||
| 666 | ;; and only the size is present in OS/2 -l format. | ||
| 667 | "^..[drwxlts-]+ \\( *[0-9]+\\( [^ ]+ +\\([^ ]+ +\\)?[0-9]+\\)?\\) " | ||
| 668 | (line-end-position) t) | ||
| 669 | (replace-match (substring (concat vc-info " ") 0 10) | ||
| 670 | t t nil 1))) | ||
| 671 | |||
| 672 | ;; FIXME: VCS-specific knowledge in here needs to be factored out | ||
| 673 | (defun vc-dired-ignorable-p (filename) | ||
| 674 | "Should FILENAME be ignored in VC-Dired listings?" | ||
| 675 | (catch t | ||
| 676 | ;; Ignore anything that wouldn't be found by completion (.o, .la, etc.) | ||
| 677 | (dolist (ignorable completion-ignored-extensions) | ||
| 678 | (let ((ext (substring filename | ||
| 679 | (- (length filename) | ||
| 680 | (length ignorable))))) | ||
| 681 | (if (string= ignorable ext) (throw t t)))) | ||
| 682 | ;; Ignore Makefiles derived from something else | ||
| 683 | (when (string= (file-name-nondirectory filename) "Makefile") | ||
| 684 | (let* ((dir (file-name-directory filename)) | ||
| 685 | (peers (directory-files (or dir default-directory)))) | ||
| 686 | (if (or (member "Makefile.in" peers) (member "Makefile.am" peers)) | ||
| 687 | (throw t t)))) | ||
| 688 | nil)) | ||
| 689 | |||
| 690 | (defun vc-dired-purge () | ||
| 691 | "Remove empty subdirs." | ||
| 692 | (goto-char (point-min)) | ||
| 693 | (while (dired-get-subdir) | ||
| 694 | (forward-line 2) | ||
| 695 | (if (dired-get-filename nil t) | ||
| 696 | (if (not (dired-next-subdir 1 t)) | ||
| 697 | (goto-char (point-max))) | ||
| 698 | (forward-line -2) | ||
| 699 | (if (not (string= (dired-current-directory) default-directory)) | ||
| 700 | (dired-do-kill-lines t "") | ||
| 701 | ;; We cannot remove the top level directory. | ||
| 702 | ;; Just make it look a little nicer. | ||
| 703 | (forward-line 1) | ||
| 704 | (or (eobp) (kill-line)) | ||
| 705 | (if (not (dired-next-subdir 1 t)) | ||
| 706 | (goto-char (point-max)))))) | ||
| 707 | (goto-char (point-min))) | ||
| 708 | |||
| 709 | (defun vc-dired-buffers-for-dir (dir) | ||
| 710 | "Return a list of all vc-dired buffers that currently display DIR." | ||
| 711 | (let (result) | ||
| 712 | ;; Check whether dired is loaded. | ||
| 713 | (when (fboundp 'dired-buffers-for-dir) | ||
| 714 | (dolist (buffer (dired-buffers-for-dir dir)) | ||
| 715 | (with-current-buffer buffer | ||
| 716 | (when vc-dired-mode | ||
| 717 | (push buffer result))))) | ||
| 718 | (nreverse result))) | ||
| 719 | |||
| 720 | (defun vc-directory-resynch-file (file) | ||
| 721 | "Update the entries for FILE in any VC Dired buffers that list it." | ||
| 722 | ;;FIXME This needs to be implemented so it works for vc-dir | ||
| 723 | (let ((buffers (vc-dired-buffers-for-dir (file-name-directory file)))) | ||
| 724 | (when buffers | ||
| 725 | (mapcar (lambda (buffer) | ||
| 726 | (with-current-buffer buffer | ||
| 727 | (when (dired-goto-file file) | ||
| 728 | ;; bind vc-dired-terse-mode to nil so that | ||
| 729 | ;; files won't vanish when they are checked in | ||
| 730 | (let ((vc-dired-terse-mode nil)) | ||
| 731 | (dired-do-redisplay 1))))) | ||
| 732 | buffers)))) | ||
| 733 | |||
| 734 | ;;;###autoload | ||
| 735 | (defun vc-directory (dir read-switches) | ||
| 736 | "Create a buffer in VC Dired Mode for directory DIR. | ||
| 737 | |||
| 738 | See Info node `VC Dired Mode'. | ||
| 739 | |||
| 740 | With prefix arg READ-SWITCHES, specify a value to override | ||
| 741 | `dired-listing-switches' when generating the listing." | ||
| 742 | (interactive "DDired under VC (directory): \nP") | ||
| 743 | (let ((vc-dired-switches (concat vc-dired-listing-switches | ||
| 744 | (if vc-dired-recurse "R" "")))) | ||
| 745 | (if read-switches | ||
| 746 | (setq vc-dired-switches | ||
| 747 | (read-string "Dired listing switches: " | ||
| 748 | vc-dired-switches))) | ||
| 749 | (require 'dired) | ||
| 750 | (require 'dired-aux) | ||
| 751 | (switch-to-buffer | ||
| 752 | (dired-internal-noselect (expand-file-name (file-name-as-directory dir)) | ||
| 753 | vc-dired-switches | ||
| 754 | 'vc-dired-mode)))) | ||
| 755 | |||
| 756 | ;; VC status implementation | ||
| 757 | |||
| 758 | ;; Used to store information for the files displayed in the *VC status* buffer. | ||
| 759 | ;; Each item displayed corresponds to one of these defstructs. | ||
| 760 | (defstruct (vc-dir-fileinfo | ||
| 761 | (:copier nil) | ||
| 762 | (:type list) ;So we can use `member' on lists of FIs. | ||
| 763 | (:constructor | ||
| 764 | ;; We could define it as an alias for `list'. | ||
| 765 | vc-dir-create-fileinfo (name state &optional extra marked directory)) | ||
| 766 | (:conc-name vc-dir-fileinfo->)) | ||
| 767 | name ;Keep it as first, for `member'. | ||
| 768 | state | ||
| 769 | ;; For storing backend specific information. | ||
| 770 | extra | ||
| 771 | marked | ||
| 772 | ;; To keep track of not updated files during a global refresh | ||
| 773 | needs-update | ||
| 774 | ;; To distinguish files and directories. | ||
| 775 | directory) | ||
| 776 | |||
| 777 | (defvar vc-ewoc nil) | ||
| 778 | |||
| 779 | (defun vc-default-status-extra-headers (backend dir) | ||
| 780 | ;; Be loud by default to remind people to add coded to display | ||
| 781 | ;; backend specific headers. | ||
| 782 | ;; XXX: change this to return nil before the release. | ||
| 783 | "Extra : Add backend specific headers here") | ||
| 784 | |||
| 785 | (defun vc-dir-headers (backend dir) | ||
| 786 | "Display the headers in the *VC status* buffer. | ||
| 787 | It calls the `status-extra-headers' backend method to display backend | ||
| 788 | specific headers." | ||
| 789 | (concat | ||
| 790 | (propertize "VC backend : " 'face 'font-lock-type-face) | ||
| 791 | (propertize (format "%s\n" backend) 'face 'font-lock-variable-name-face) | ||
| 792 | (propertize "Working dir: " 'face 'font-lock-type-face) | ||
| 793 | (propertize (format "%s\n" dir) 'face 'font-lock-variable-name-face) | ||
| 794 | (vc-call-backend backend 'status-extra-headers dir) | ||
| 795 | "\n")) | ||
| 796 | |||
| 797 | (defun vc-default-status-printer (backend fileentry) | ||
| 798 | "Pretty print FILEENTRY." | ||
| 799 | ;; If you change the layout here, change vc-dir-move-to-goal-column. | ||
| 800 | (let ((state | ||
| 801 | (if (vc-dir-fileinfo->directory fileentry) | ||
| 802 | 'DIRECTORY | ||
| 803 | (vc-dir-fileinfo->state fileentry)))) | ||
| 804 | (insert | ||
| 805 | (propertize | ||
| 806 | (format "%c" (if (vc-dir-fileinfo->marked fileentry) ?* ? )) | ||
| 807 | 'face 'font-lock-type-face) | ||
| 808 | " " | ||
| 809 | (propertize | ||
| 810 | (format "%-20s" state) | ||
| 811 | 'face (cond ((eq state 'up-to-date) 'font-lock-builtin-face) | ||
| 812 | ((memq state '(missing conflict)) 'font-lock-warning-face) | ||
| 813 | (t 'font-lock-variable-name-face)) | ||
| 814 | 'mouse-face 'highlight) | ||
| 815 | " " | ||
| 816 | (propertize | ||
| 817 | (format "%s" (vc-dir-fileinfo->name fileentry)) | ||
| 818 | 'face 'font-lock-function-name-face | ||
| 819 | 'mouse-face 'highlight)))) | ||
| 820 | |||
| 821 | (defun vc-dir-printer (fileentry) | ||
| 822 | (let ((backend (vc-responsible-backend default-directory))) | ||
| 823 | (vc-call-backend backend 'status-printer fileentry))) | ||
| 824 | |||
| 825 | (defun vc-dir-move-to-goal-column () | ||
| 826 | ;; Used to keep the cursor on the file name column. | ||
| 827 | (beginning-of-line) | ||
| 828 | ;; Must be in sync with vc-default-status-printer. | ||
| 829 | (forward-char 25)) | ||
| 830 | |||
| 831 | (defun vc-dir-prepare-status-buffer (dir &optional create-new) | ||
| 832 | "Find a *vc-dir* buffer showing DIR, or create a new one." | ||
| 833 | (setq dir (expand-file-name dir)) | ||
| 834 | (let* ((bname "*vc-dir*") | ||
| 835 | ;; Look for another *vc-dir* buffer visiting the same directory. | ||
| 836 | (buf (save-excursion | ||
| 837 | (unless create-new | ||
| 838 | (dolist (buffer (buffer-list)) | ||
| 839 | (set-buffer buffer) | ||
| 840 | (when (and (eq major-mode 'vc-dir-mode) | ||
| 841 | (string= (expand-file-name default-directory) dir)) | ||
| 842 | (return buffer))))))) | ||
| 843 | (or buf | ||
| 844 | ;; Create a new *vc-dir* buffer. | ||
| 845 | (with-current-buffer (create-file-buffer bname) | ||
| 846 | (cd dir) | ||
| 847 | (vc-setup-buffer (current-buffer)) | ||
| 848 | ;; Reset the vc-parent-buffer-name so that it does not appear | ||
| 849 | ;; in the mode-line. | ||
| 850 | (setq vc-parent-buffer-name nil) | ||
| 851 | (current-buffer))))) | ||
| 852 | |||
| 853 | ;;;###autoload | ||
| 854 | (defun vc-dir (dir) | ||
| 855 | "Show the VC status for DIR." | ||
| 856 | (interactive "DVC status for directory: ") | ||
| 857 | (pop-to-buffer (vc-dir-prepare-status-buffer dir)) | ||
| 858 | (if (eq major-mode 'vc-dir-mode) | ||
| 859 | (vc-dir-refresh) | ||
| 860 | (vc-dir-mode))) | ||
| 861 | |||
| 862 | (defvar vc-dir-menu-map | ||
| 863 | (let ((map (make-sparse-keymap "VC-dir"))) | ||
| 864 | (define-key map [quit] | ||
| 865 | '(menu-item "Quit" quit-window | ||
| 866 | :help "Quit")) | ||
| 867 | (define-key map [kill] | ||
| 868 | '(menu-item "Kill Update Command" vc-dir-kill-dir-status-process | ||
| 869 | :enable (vc-dir-busy) | ||
| 870 | :help "Kill the command that updates VC status buffer")) | ||
| 871 | (define-key map [refresh] | ||
| 872 | '(menu-item "Refresh" vc-dir-refresh | ||
| 873 | :enable (not (vc-dir-busy)) | ||
| 874 | :help "Refresh the contents of the VC status buffer")) | ||
| 875 | (define-key map [remup] | ||
| 876 | '(menu-item "Hide up-to-date" vc-dir-hide-up-to-date | ||
| 877 | :help "Hide up-to-date items from display")) | ||
| 878 | ;; Movement. | ||
| 879 | (define-key map [sepmv] '("--")) | ||
| 880 | (define-key map [next-line] | ||
| 881 | '(menu-item "Next line" vc-dir-next-line | ||
| 882 | :help "Go to the next line" :keys "n")) | ||
| 883 | (define-key map [previous-line] | ||
| 884 | '(menu-item "Previous line" vc-dir-previous-line | ||
| 885 | :help "Go to the previous line")) | ||
| 886 | ;; Marking. | ||
| 887 | (define-key map [sepmrk] '("--")) | ||
| 888 | (define-key map [unmark-all] | ||
| 889 | '(menu-item "Unmark All" vc-dir-unmark-all-files | ||
| 890 | :help "Unmark all files that are in the same state as the current file\ | ||
| 891 | \nWith prefix argument unmark all files")) | ||
| 892 | (define-key map [unmark-previous] | ||
| 893 | '(menu-item "Unmark previous " vc-dir-unmark-file-up | ||
| 894 | :help "Move to the previous line and unmark the file")) | ||
| 895 | |||
| 896 | (define-key map [mark-all] | ||
| 897 | '(menu-item "Mark All" vc-dir-mark-all-files | ||
| 898 | :help "Mark all files that are in the same state as the current file\ | ||
| 899 | \nWith prefix argument mark all files")) | ||
| 900 | (define-key map [unmark] | ||
| 901 | '(menu-item "Unmark" vc-dir-unmark | ||
| 902 | :help "Unmark the current file or all files in the region")) | ||
| 903 | |||
| 904 | (define-key map [mark] | ||
| 905 | '(menu-item "Mark" vc-dir-mark | ||
| 906 | :help "Mark the current file or all files in the region")) | ||
| 907 | |||
| 908 | (define-key map [sepopn] '("--")) | ||
| 909 | (define-key map [open-other] | ||
| 910 | '(menu-item "Open in other window" vc-dir-find-file-other-window | ||
| 911 | :help "Find the file on the current line, in another window")) | ||
| 912 | (define-key map [open] | ||
| 913 | '(menu-item "Open file" vc-dir-find-file | ||
| 914 | :help "Find the file on the current line")) | ||
| 915 | ;; VC info details | ||
| 916 | (define-key map [sepvcdet] '("--")) | ||
| 917 | ;; FIXME: This needs a key binding. And maybe a better name | ||
| 918 | ;; ("Insert" like PCL-CVS uses does not sound that great either)... | ||
| 919 | (define-key map [ins] | ||
| 920 | '(menu-item "Show File" vc-dir-show-fileentry | ||
| 921 | :help "Show a file in the VC status listing even though it might be up to date")) | ||
| 922 | (define-key map [annotate] | ||
| 923 | '(menu-item "Annotate" vc-annotate | ||
| 924 | :help "Display the edit history of the current file using colors")) | ||
| 925 | (define-key map [diff] | ||
| 926 | '(menu-item "Compare with Base Version" vc-diff | ||
| 927 | :help "Compare file set with the base version")) | ||
| 928 | (define-key map [log] | ||
| 929 | '(menu-item "Show history" vc-print-log | ||
| 930 | :help "List the change log of the current file set in a window")) | ||
| 931 | ;; VC commands. | ||
| 932 | (define-key map [sepvccmd] '("--")) | ||
| 933 | (define-key map [update] | ||
| 934 | '(menu-item "Update to latest version" vc-update | ||
| 935 | :help "Update the current fileset's files to their tip revisions")) | ||
| 936 | (define-key map [revert] | ||
| 937 | '(menu-item "Revert to base version" vc-revert | ||
| 938 | :help "Revert working copies of the selected fileset to their repository contents.")) | ||
| 939 | (define-key map [next-action] | ||
| 940 | ;; FIXME: This really really really needs a better name! | ||
| 941 | ;; And a key binding too. | ||
| 942 | '(menu-item "Check In/Out" vc-next-action | ||
| 943 | :help "Do the next logical version control operation on the current fileset")) | ||
| 944 | (define-key map [register] | ||
| 945 | '(menu-item "Register" vc-dir-register | ||
| 946 | :help "Register file set into the version control system")) | ||
| 947 | map) | ||
| 948 | "Menu for VC status") | ||
| 949 | |||
| 950 | (defalias 'vc-dir-menu-map vc-dir-menu-map) | ||
| 951 | |||
| 952 | (defvar vc-dir-mode-map | ||
| 953 | (let ((map (make-keymap))) | ||
| 954 | (suppress-keymap map) | ||
| 955 | ;; Marking. | ||
| 956 | (define-key map "m" 'vc-dir-mark) | ||
| 957 | (define-key map "M" 'vc-dir-mark-all-files) | ||
| 958 | (define-key map "u" 'vc-dir-unmark) | ||
| 959 | (define-key map "U" 'vc-dir-unmark-all-files) | ||
| 960 | (define-key map "\C-?" 'vc-dir-unmark-file-up) | ||
| 961 | (define-key map "\M-\C-?" 'vc-dir-unmark-all-files) | ||
| 962 | ;; Movement. | ||
| 963 | (define-key map "n" 'vc-dir-next-line) | ||
| 964 | (define-key map " " 'vc-dir-next-line) | ||
| 965 | (define-key map "\t" 'vc-dir-next-line) | ||
| 966 | (define-key map "p" 'vc-dir-previous-line) | ||
| 967 | (define-key map [backtab] 'vc-dir-previous-line) | ||
| 968 | ;; VC commands. | ||
| 969 | (define-key map "=" 'vc-diff) ;; C-x v = | ||
| 970 | (define-key map "a" 'vc-dir-register) | ||
| 971 | (define-key map "+" 'vc-update) ;; C-x v + | ||
| 972 | (define-key map "R" 'vc-revert) ;; u is taken by unmark. | ||
| 973 | |||
| 974 | ;; Can't be "g" (as in vc map), so "A" for "Annotate". | ||
| 975 | (define-key map "A" 'vc-annotate) | ||
| 976 | (define-key map "l" 'vc-print-log) ;; C-x v l | ||
| 977 | ;; The remainder. | ||
| 978 | (define-key map "f" 'vc-dir-find-file) | ||
| 979 | (define-key map "\C-m" 'vc-dir-find-file) | ||
| 980 | (define-key map "o" 'vc-dir-find-file-other-window) | ||
| 981 | (define-key map "x" 'vc-dir-hide-up-to-date) | ||
| 982 | (define-key map "q" 'quit-window) | ||
| 983 | (define-key map "g" 'vc-dir-refresh) | ||
| 984 | (define-key map "\C-c\C-c" 'vc-dir-kill-dir-status-process) | ||
| 985 | (define-key map [(down-mouse-3)] 'vc-dir-menu) | ||
| 986 | (define-key map [(mouse-2)] 'vc-dir-toggle-mark) | ||
| 987 | |||
| 988 | ;; Hook up the menu. | ||
| 989 | (define-key map [menu-bar vc-dir-mode] | ||
| 990 | '(menu-item | ||
| 991 | ;; This is used to that VC backends could add backend specific | ||
| 992 | ;; menu items to vc-dir-menu-map. | ||
| 993 | "VC Status" vc-dir-menu-map :filter vc-dir-menu-map-filter)) | ||
| 994 | map) | ||
| 995 | "Keymap for VC status") | ||
| 996 | |||
| 997 | (defun vc-default-extra-status-menu (backend) | ||
| 998 | nil) | ||
| 999 | |||
| 1000 | ;; This is used to that VC backends could add backend specific menu | ||
| 1001 | ;; items to vc-dir-menu-map. | ||
| 1002 | (defun vc-dir-menu-map-filter (orig-binding) | ||
| 1003 | (when (and (symbolp orig-binding) (fboundp orig-binding)) | ||
| 1004 | (setq orig-binding (indirect-function orig-binding))) | ||
| 1005 | (let ((ext-binding | ||
| 1006 | (vc-call-backend (vc-responsible-backend default-directory) | ||
| 1007 | 'extra-status-menu))) | ||
| 1008 | (if (null ext-binding) | ||
| 1009 | orig-binding | ||
| 1010 | (append orig-binding | ||
| 1011 | '("----") | ||
| 1012 | ext-binding)))) | ||
| 1013 | |||
| 1014 | (defmacro vc-at-event (event &rest body) | ||
| 1015 | "Evaluate `body' wich point located at event-start of `event'. | ||
| 1016 | If `body' uses `event', it should be a variable, | ||
| 1017 | otherwise it will be evaluated twice." | ||
| 1018 | (let ((posn (gensym "vc-at-event-posn"))) | ||
| 1019 | `(let ((,posn (event-start ,event))) | ||
| 1020 | (save-excursion | ||
| 1021 | (set-buffer (window-buffer (posn-window ,posn))) | ||
| 1022 | (goto-char (posn-point ,posn)) | ||
| 1023 | ,@body)))) | ||
| 1024 | |||
| 1025 | (defun vc-dir-menu (e) | ||
| 1026 | "Popup the VC status menu." | ||
| 1027 | (interactive "e") | ||
| 1028 | (vc-at-event e (popup-menu vc-dir-menu-map e))) | ||
| 1029 | |||
| 1030 | (defvar vc-dir-tool-bar-map | ||
| 1031 | (let ((map (make-sparse-keymap))) | ||
| 1032 | (tool-bar-local-item-from-menu 'vc-dir-find-file "open" | ||
| 1033 | map vc-dir-mode-map) | ||
| 1034 | (tool-bar-local-item "bookmark_add" | ||
| 1035 | 'vc-dir-toggle-mark 'vc-dir-toggle-mark map | ||
| 1036 | :help "Toggle mark on current item") | ||
| 1037 | (tool-bar-local-item-from-menu 'vc-dir-previous-line "left-arrow" | ||
| 1038 | map vc-dir-mode-map | ||
| 1039 | :rtl "right-arrow") | ||
| 1040 | (tool-bar-local-item-from-menu 'vc-dir-next-line "right-arrow" | ||
| 1041 | map vc-dir-mode-map | ||
| 1042 | :rtl "left-arrow") | ||
| 1043 | (tool-bar-local-item-from-menu 'vc-print-log "info" | ||
| 1044 | map vc-dir-mode-map) | ||
| 1045 | (tool-bar-local-item-from-menu 'vc-dir-refresh "refresh" | ||
| 1046 | map vc-dir-mode-map) | ||
| 1047 | (tool-bar-local-item-from-menu 'nonincremental-search-forward | ||
| 1048 | "search" map) | ||
| 1049 | (tool-bar-local-item-from-menu 'vc-dir-kill-dir-status-process "cancel" | ||
| 1050 | map vc-dir-mode-map) | ||
| 1051 | (tool-bar-local-item-from-menu 'quit-window "exit" | ||
| 1052 | map vc-dir-mode-map) | ||
| 1053 | map)) | ||
| 1054 | |||
| 1055 | (defvar vc-dir-process-buffer nil | ||
| 1056 | "The buffer used for the asynchronous call that computes the VC status.") | ||
| 1057 | |||
| 1058 | (defun vc-dir-mode () | ||
| 1059 | "Major mode for showing the VC status for a directory. | ||
| 1060 | Marking/Unmarking key bindings and actions: | ||
| 1061 | m - marks a file/directory or ff the region is active, mark all the files | ||
| 1062 | in region. | ||
| 1063 | Restrictions: - a file cannot be marked if any parent directory is marked | ||
| 1064 | - a directory cannot be marked if any child file or | ||
| 1065 | directory is marked | ||
| 1066 | u - marks a file/directory or if the region is active, unmark all the files | ||
| 1067 | in region. | ||
| 1068 | M - if the cursor is on a file: mark all the files with the same VC state as | ||
| 1069 | the current file | ||
| 1070 | - if the cursor is on a directory: mark all child files | ||
| 1071 | - with a prefix argument: mark all files | ||
| 1072 | U - if the cursor is on a file: unmark all the files with the same VC state | ||
| 1073 | as the current file | ||
| 1074 | - if the cursor is on a directory: unmark all child files | ||
| 1075 | - with a prefix argument: unmark all files | ||
| 1076 | |||
| 1077 | |||
| 1078 | \\{vc-dir-mode-map}" | ||
| 1079 | (setq mode-name "VC Status") | ||
| 1080 | (setq major-mode 'vc-dir-mode) | ||
| 1081 | (setq buffer-read-only t) | ||
| 1082 | (use-local-map vc-dir-mode-map) | ||
| 1083 | (set (make-local-variable 'tool-bar-map) vc-dir-tool-bar-map) | ||
| 1084 | (let ((buffer-read-only nil) | ||
| 1085 | (backend (vc-responsible-backend default-directory)) | ||
| 1086 | entries) | ||
| 1087 | (erase-buffer) | ||
| 1088 | (set (make-local-variable 'vc-dir-process-buffer) nil) | ||
| 1089 | (set (make-local-variable 'vc-ewoc) | ||
| 1090 | (ewoc-create #'vc-dir-printer | ||
| 1091 | (vc-dir-headers backend default-directory))) | ||
| 1092 | (add-hook 'after-save-hook 'vc-dir-mark-buffer-changed) | ||
| 1093 | ;; Make sure that if the VC status buffer is killed, the update | ||
| 1094 | ;; process running in the background is also killed. | ||
| 1095 | (add-hook 'kill-buffer-query-functions 'vc-dir-kill-query nil t) | ||
| 1096 | (vc-dir-refresh)) | ||
| 1097 | (run-hooks 'vc-dir-mode-hook)) | ||
| 1098 | |||
| 1099 | (put 'vc-dir-mode 'mode-class 'special) | ||
| 1100 | |||
| 1101 | ;; t if directories should be shown in vc-dir. | ||
| 1102 | ;; WORK IN PROGRESS! DO NOT SET this! ONLY set it if you want to help | ||
| 1103 | ;; write code for this feature. This variable will likely disappear | ||
| 1104 | ;; when the work is done. | ||
| 1105 | (defvar vc-dir-insert-directories nil) | ||
| 1106 | |||
| 1107 | (defun vc-dir-update (entries buffer &optional noinsert) | ||
| 1108 | "Update BUFFER's ewoc from the list of ENTRIES. | ||
| 1109 | If NOINSERT, ignore elements on ENTRIES which are not in the ewoc." | ||
| 1110 | ;; Add ENTRIES to the vc-dir buffer BUFFER. | ||
| 1111 | (with-current-buffer buffer | ||
| 1112 | ;; Insert the entries sorted by name into the ewoc. | ||
| 1113 | ;; We assume the ewoc is sorted too, which should be the | ||
| 1114 | ;; case if we always add entries with vc-dir-update. | ||
| 1115 | (setq entries | ||
| 1116 | ;; Sort: first files and then subdirectories. | ||
| 1117 | ;; XXX: this is VERY inefficient, it computes the directory | ||
| 1118 | ;; names too many times | ||
| 1119 | (sort entries | ||
| 1120 | (lambda (entry1 entry2) | ||
| 1121 | (let ((dir1 (file-name-directory (expand-file-name (car entry1)))) | ||
| 1122 | (dir2 (file-name-directory (expand-file-name (car entry2))))) | ||
| 1123 | (cond | ||
| 1124 | ((string< dir1 dir2) t) | ||
| 1125 | ((not (string= dir1 dir2)) nil) | ||
| 1126 | ((string< (car entry1) (car entry2)))))))) | ||
| 1127 | (if (not vc-dir-insert-directories) | ||
| 1128 | (let ((entry (car entries)) | ||
| 1129 | (node (ewoc-nth vc-ewoc 0))) | ||
| 1130 | (while (and entry node) | ||
| 1131 | (let ((entryfile (car entry)) | ||
| 1132 | (nodefile (vc-dir-fileinfo->name (ewoc-data node)))) | ||
| 1133 | (cond | ||
| 1134 | ((string-lessp nodefile entryfile) | ||
| 1135 | (setq node (ewoc-next vc-ewoc node))) | ||
| 1136 | ((string-lessp entryfile nodefile) | ||
| 1137 | (unless noinsert | ||
| 1138 | (ewoc-enter-before vc-ewoc node | ||
| 1139 | (apply 'vc-dir-create-fileinfo entry))) | ||
| 1140 | (setq entries (cdr entries) entry (car entries))) | ||
| 1141 | (t | ||
| 1142 | (setf (vc-dir-fileinfo->state (ewoc-data node)) (nth 1 entry)) | ||
| 1143 | (setf (vc-dir-fileinfo->extra (ewoc-data node)) (nth 2 entry)) | ||
| 1144 | (setf (vc-dir-fileinfo->needs-update (ewoc-data node)) nil) | ||
| 1145 | (ewoc-invalidate vc-ewoc node) | ||
| 1146 | (setq entries (cdr entries) entry (car entries)) | ||
| 1147 | (setq node (ewoc-next vc-ewoc node)))))) | ||
| 1148 | (unless (or node noinsert) | ||
| 1149 | ;; We're past the last node, all remaining entries go to the end. | ||
| 1150 | (while entries | ||
| 1151 | (ewoc-enter-last vc-ewoc | ||
| 1152 | (apply 'vc-dir-create-fileinfo (pop entries)))))) | ||
| 1153 | ;; Insert directory entries in the right places. | ||
| 1154 | (let ((entry (car entries)) | ||
| 1155 | (node (ewoc-nth vc-ewoc 0))) | ||
| 1156 | ;; Insert . if it is not present. | ||
| 1157 | (unless node | ||
| 1158 | (let ((rd (file-relative-name default-directory))) | ||
| 1159 | (ewoc-enter-last | ||
| 1160 | vc-ewoc (vc-dir-create-fileinfo | ||
| 1161 | rd nil nil nil (expand-file-name default-directory)))) | ||
| 1162 | (setq node (ewoc-nth vc-ewoc 0))) | ||
| 1163 | |||
| 1164 | (while (and entry node) | ||
| 1165 | (let* ((entryfile (car entry)) | ||
| 1166 | (entrydir (file-name-directory (expand-file-name entryfile))) | ||
| 1167 | (nodedir | ||
| 1168 | (or (vc-dir-fileinfo->directory (ewoc-data node)) | ||
| 1169 | (file-name-directory | ||
| 1170 | (expand-file-name | ||
| 1171 | (vc-dir-fileinfo->name (ewoc-data node))))))) | ||
| 1172 | (cond | ||
| 1173 | ;; First try to find the directory. | ||
| 1174 | ((string-lessp nodedir entrydir) | ||
| 1175 | (setq node (ewoc-next vc-ewoc node))) | ||
| 1176 | ((string-equal nodedir entrydir) | ||
| 1177 | ;; Found the directory, find the place for the file name. | ||
| 1178 | (let ((nodefile (vc-dir-fileinfo->name (ewoc-data node)))) | ||
| 1179 | (cond | ||
| 1180 | ((string-lessp nodefile entryfile) | ||
| 1181 | (setq node (ewoc-next vc-ewoc node))) | ||
| 1182 | ((string-equal nodefile entryfile) | ||
| 1183 | (setf (vc-dir-fileinfo->state (ewoc-data node)) (nth 1 entry)) | ||
| 1184 | (setf (vc-dir-fileinfo->extra (ewoc-data node)) (nth 2 entry)) | ||
| 1185 | (setf (vc-dir-fileinfo->needs-update (ewoc-data node)) nil) | ||
| 1186 | (ewoc-invalidate vc-ewoc node) | ||
| 1187 | (setq entries (cdr entries) entry (car entries)) | ||
| 1188 | (setq node (ewoc-next vc-ewoc node))) | ||
| 1189 | (t | ||
| 1190 | (ewoc-enter-before vc-ewoc node | ||
| 1191 | (apply 'vc-dir-create-fileinfo entry)) | ||
| 1192 | (setq entries (cdr entries) entry (car entries)))))) | ||
| 1193 | (t | ||
| 1194 | ;; We need to insert a directory node | ||
| 1195 | (let ((rd (file-relative-name entrydir))) | ||
| 1196 | (ewoc-enter-last | ||
| 1197 | vc-ewoc (vc-dir-create-fileinfo rd nil nil nil entrydir))) | ||
| 1198 | ;; Now insert the node itself. | ||
| 1199 | (ewoc-enter-before vc-ewoc node | ||
| 1200 | (apply 'vc-dir-create-fileinfo entry)) | ||
| 1201 | (setq entries (cdr entries) entry (car entries)))))) | ||
| 1202 | ;; We're past the last node, all remaining entries go to the end. | ||
| 1203 | (unless (or node noinsert) | ||
| 1204 | (let* ((lastnode (ewoc-nth vc-ewoc -1)) | ||
| 1205 | (lastdir | ||
| 1206 | (or (vc-dir-fileinfo->directory (ewoc-data lastnode)) | ||
| 1207 | (file-name-directory | ||
| 1208 | (expand-file-name | ||
| 1209 | (vc-dir-fileinfo->name (ewoc-data lastnode))))))) | ||
| 1210 | (dolist (entry entries) | ||
| 1211 | (let ((entrydir (file-name-directory (expand-file-name (car entry))))) | ||
| 1212 | ;; Insert a directory node if needed. | ||
| 1213 | (unless (string-equal lastdir entrydir) | ||
| 1214 | (setq lastdir entrydir) | ||
| 1215 | (let ((rd (file-relative-name entrydir))) | ||
| 1216 | (ewoc-enter-last | ||
| 1217 | vc-ewoc (vc-dir-create-fileinfo rd nil nil nil entrydir)))) | ||
| 1218 | ;; Now insert the node itself. | ||
| 1219 | (ewoc-enter-last vc-ewoc | ||
| 1220 | (apply 'vc-dir-create-fileinfo entry)))))))))) | ||
| 1221 | |||
| 1222 | (defun vc-dir-busy () | ||
| 1223 | (and (buffer-live-p vc-dir-process-buffer) | ||
| 1224 | (get-buffer-process vc-dir-process-buffer))) | ||
| 1225 | |||
| 1226 | (defun vc-dir-refresh-files (files default-state) | ||
| 1227 | "Refresh some files in the VC status buffer." | ||
| 1228 | (let ((backend (vc-responsible-backend default-directory)) | ||
| 1229 | (status-buffer (current-buffer)) | ||
| 1230 | (def-dir default-directory)) | ||
| 1231 | (vc-set-mode-line-busy-indicator) | ||
| 1232 | ;; Call the `dir-status-file' backend function. | ||
| 1233 | ;; `dir-status-file' is supposed to be asynchronous. | ||
| 1234 | ;; It should compute the results, and then call the function | ||
| 1235 | ;; passed as an argument in order to update the vc-dir buffer | ||
| 1236 | ;; with the results. | ||
| 1237 | (unless (buffer-live-p vc-dir-process-buffer) | ||
| 1238 | (setq vc-dir-process-buffer | ||
| 1239 | (generate-new-buffer (format " *VC-%s* tmp status" backend)))) | ||
| 1240 | (lexical-let ((buffer (current-buffer))) | ||
| 1241 | (with-current-buffer vc-dir-process-buffer | ||
| 1242 | (cd def-dir) | ||
| 1243 | (erase-buffer) | ||
| 1244 | (vc-call-backend | ||
| 1245 | backend 'dir-status-files def-dir files default-state | ||
| 1246 | (lambda (entries &optional more-to-come) | ||
| 1247 | ;; ENTRIES is a list of (FILE VC_STATE EXTRA) items. | ||
| 1248 | ;; If MORE-TO-COME is true, then more updates will come from | ||
| 1249 | ;; the asynchronous process. | ||
| 1250 | (with-current-buffer buffer | ||
| 1251 | (vc-dir-update entries buffer) | ||
| 1252 | (unless more-to-come | ||
| 1253 | (setq mode-line-process nil) | ||
| 1254 | ;; Remove the ones that haven't been updated at all. | ||
| 1255 | ;; Those not-updated are those whose state is nil because the | ||
| 1256 | ;; file/dir doesn't exist and isn't versioned. | ||
| 1257 | (ewoc-filter vc-ewoc | ||
| 1258 | (lambda (info) | ||
| 1259 | (not (vc-dir-fileinfo->needs-update info)))))))))))) | ||
| 1260 | |||
| 1261 | (defun vc-dir-refresh () | ||
| 1262 | "Refresh the contents of the VC status buffer. | ||
| 1263 | Throw an error if another update process is in progress." | ||
| 1264 | (interactive) | ||
| 1265 | (if (vc-dir-busy) | ||
| 1266 | (error "Another update process is in progress, cannot run two at a time") | ||
| 1267 | (let ((backend (vc-responsible-backend default-directory)) | ||
| 1268 | (status-buffer (current-buffer)) | ||
| 1269 | (def-dir default-directory)) | ||
| 1270 | (vc-set-mode-line-busy-indicator) | ||
| 1271 | ;; Call the `dir-status' backend function. | ||
| 1272 | ;; `dir-status' is supposed to be asynchronous. | ||
| 1273 | ;; It should compute the results, and then call the function | ||
| 1274 | ;; passed as an argument in order to update the vc-dir buffer | ||
| 1275 | ;; with the results. | ||
| 1276 | |||
| 1277 | ;; Create a buffer that can be used by `dir-status' and call | ||
| 1278 | ;; `dir-status' with this buffer as the current buffer. Use | ||
| 1279 | ;; `vc-dir-process-buffer' to remember this buffer, so that | ||
| 1280 | ;; it can be used later to kill the update process in case it | ||
| 1281 | ;; takes too long. | ||
| 1282 | (unless (buffer-live-p vc-dir-process-buffer) | ||
| 1283 | (setq vc-dir-process-buffer | ||
| 1284 | (generate-new-buffer (format " *VC-%s* tmp status" backend)))) | ||
| 1285 | ;; set the needs-update flag on all entries | ||
| 1286 | (ewoc-map (lambda (info) (setf (vc-dir-fileinfo->needs-update info) t) nil) | ||
| 1287 | vc-ewoc) | ||
| 1288 | (lexical-let ((buffer (current-buffer))) | ||
| 1289 | (with-current-buffer vc-dir-process-buffer | ||
| 1290 | (cd def-dir) | ||
| 1291 | (erase-buffer) | ||
| 1292 | (vc-call-backend | ||
| 1293 | backend 'dir-status def-dir | ||
| 1294 | (lambda (entries &optional more-to-come) | ||
| 1295 | ;; ENTRIES is a list of (FILE VC_STATE EXTRA) items. | ||
| 1296 | ;; If MORE-TO-COME is true, then more updates will come from | ||
| 1297 | ;; the asynchronous process. | ||
| 1298 | (with-current-buffer buffer | ||
| 1299 | (vc-dir-update entries buffer) | ||
| 1300 | (unless more-to-come | ||
| 1301 | (let ((remaining | ||
| 1302 | (ewoc-collect | ||
| 1303 | vc-ewoc 'vc-dir-fileinfo->needs-update))) | ||
| 1304 | (if remaining | ||
| 1305 | (vc-dir-refresh-files | ||
| 1306 | (mapcar 'vc-dir-fileinfo->name remaining) | ||
| 1307 | 'up-to-date) | ||
| 1308 | (setq mode-line-process nil)))))))))))) | ||
| 1309 | |||
| 1310 | (defun vc-dir-kill-dir-status-process () | ||
| 1311 | "Kill the temporary buffer and associated process." | ||
| 1312 | (interactive) | ||
| 1313 | (when (buffer-live-p vc-dir-process-buffer) | ||
| 1314 | (let ((proc (get-buffer-process vc-dir-process-buffer))) | ||
| 1315 | (when proc (delete-process proc)) | ||
| 1316 | (setq vc-dir-process-buffer nil) | ||
| 1317 | (setq mode-line-process nil)))) | ||
| 1318 | |||
| 1319 | (defun vc-dir-kill-query () | ||
| 1320 | ;; Make sure that when the VC status buffer is killed the update | ||
| 1321 | ;; process running in background is also killed. | ||
| 1322 | (if (vc-dir-busy) | ||
| 1323 | (when (y-or-n-p "Status update process running, really kill status buffer?") | ||
| 1324 | (vc-dir-kill-dir-status-process) | ||
| 1325 | t) | ||
| 1326 | t)) | ||
| 1327 | |||
| 1328 | (defun vc-dir-next-line (arg) | ||
| 1329 | "Go to the next line. | ||
| 1330 | If a prefix argument is given, move by that many lines." | ||
| 1331 | (interactive "p") | ||
| 1332 | (ewoc-goto-next vc-ewoc arg) | ||
| 1333 | (vc-dir-move-to-goal-column)) | ||
| 1334 | |||
| 1335 | (defun vc-dir-previous-line (arg) | ||
| 1336 | "Go to the previous line. | ||
| 1337 | If a prefix argument is given, move by that many lines." | ||
| 1338 | (interactive "p") | ||
| 1339 | (ewoc-goto-prev vc-ewoc arg) | ||
| 1340 | (vc-dir-move-to-goal-column)) | ||
| 1341 | |||
| 1342 | (defun vc-dir-mark-unmark (mark-unmark-function) | ||
| 1343 | (if (use-region-p) | ||
| 1344 | (let ((firstl (line-number-at-pos (region-beginning))) | ||
| 1345 | (lastl (line-number-at-pos (region-end)))) | ||
| 1346 | (save-excursion | ||
| 1347 | (goto-char (region-beginning)) | ||
| 1348 | (while (<= (line-number-at-pos) lastl) | ||
| 1349 | (funcall mark-unmark-function)))) | ||
| 1350 | (funcall mark-unmark-function))) | ||
| 1351 | |||
| 1352 | (defun vc-dir-parent-marked-p (arg) | ||
| 1353 | (when vc-dir-insert-directories | ||
| 1354 | ;; Return nil if none of the parent directories of arg is marked. | ||
| 1355 | (let* ((argdata (ewoc-data arg)) | ||
| 1356 | (argdir | ||
| 1357 | (let ((crtdir (vc-dir-fileinfo->directory argdata))) | ||
| 1358 | (if crtdir | ||
| 1359 | crtdir | ||
| 1360 | (file-name-directory (expand-file-name | ||
| 1361 | (vc-dir-fileinfo->name argdata)))))) | ||
| 1362 | (arglen (length argdir)) | ||
| 1363 | (crt arg) | ||
| 1364 | data dir) | ||
| 1365 | ;; Go through the predecessors, checking if any directory that is | ||
| 1366 | ;; a parent is marked. | ||
| 1367 | (while (setq crt (ewoc-prev vc-ewoc crt)) | ||
| 1368 | (setq data (ewoc-data crt)) | ||
| 1369 | (setq dir | ||
| 1370 | (let ((crtdir (vc-dir-fileinfo->directory data))) | ||
| 1371 | (if crtdir | ||
| 1372 | crtdir | ||
| 1373 | (file-name-directory (expand-file-name | ||
| 1374 | (vc-dir-fileinfo->name data)))))) | ||
| 1375 | |||
| 1376 | (when (and (vc-dir-fileinfo->directory data) | ||
| 1377 | (string-equal (substring argdir 0 (length dir)) dir)) | ||
| 1378 | (when (vc-dir-fileinfo->marked data) | ||
| 1379 | (error "Cannot mark `%s', parent directory `%s' marked" | ||
| 1380 | (vc-dir-fileinfo->name argdata) | ||
| 1381 | (vc-dir-fileinfo->name data))))) | ||
| 1382 | nil))) | ||
| 1383 | |||
| 1384 | (defun vc-dir-children-marked-p (arg) | ||
| 1385 | ;; Return nil if none of the children of arg is marked. | ||
| 1386 | (when vc-dir-insert-directories | ||
| 1387 | (let* ((argdata (ewoc-data arg)) | ||
| 1388 | (argdir (vc-dir-fileinfo->directory argdata)) | ||
| 1389 | (arglen (length argdir)) | ||
| 1390 | (is-child t) | ||
| 1391 | (crt arg) | ||
| 1392 | data dir) | ||
| 1393 | (while (and is-child (setq crt (ewoc-next vc-ewoc crt))) | ||
| 1394 | (setq data (ewoc-data crt)) | ||
| 1395 | (setq dir | ||
| 1396 | (let ((crtdir (vc-dir-fileinfo->directory data))) | ||
| 1397 | (if crtdir | ||
| 1398 | crtdir | ||
| 1399 | (file-name-directory (expand-file-name | ||
| 1400 | (vc-dir-fileinfo->name data)))))) | ||
| 1401 | (if (string-equal argdir (substring dir 0 arglen)) | ||
| 1402 | (when (vc-dir-fileinfo->marked data) | ||
| 1403 | (error "Cannot mark `%s', child `%s' marked" | ||
| 1404 | (vc-dir-fileinfo->name argdata) | ||
| 1405 | (vc-dir-fileinfo->name data))) | ||
| 1406 | ;; We are done, we got to an entry that is not a child of `arg'. | ||
| 1407 | (setq is-child nil))) | ||
| 1408 | nil))) | ||
| 1409 | |||
| 1410 | (defun vc-dir-mark-file (&optional arg) | ||
| 1411 | ;; Mark ARG or the current file and move to the next line. | ||
| 1412 | (let* ((crt (or arg (ewoc-locate vc-ewoc))) | ||
| 1413 | (file (ewoc-data crt)) | ||
| 1414 | (isdir (vc-dir-fileinfo->directory file))) | ||
| 1415 | (when (or (and isdir (not (vc-dir-children-marked-p crt))) | ||
| 1416 | (and (not isdir) (not (vc-dir-parent-marked-p crt)))) | ||
| 1417 | (setf (vc-dir-fileinfo->marked file) t) | ||
| 1418 | (ewoc-invalidate vc-ewoc crt) | ||
| 1419 | (unless (or arg (mouse-event-p last-command-event)) | ||
| 1420 | (vc-dir-next-line 1))))) | ||
| 1421 | |||
| 1422 | (defun vc-dir-mark () | ||
| 1423 | "Mark the current file or all files in the region. | ||
| 1424 | If the region is active, mark all the files in the region. | ||
| 1425 | Otherwise mark the file on the current line and move to the next | ||
| 1426 | line." | ||
| 1427 | (interactive) | ||
| 1428 | (vc-dir-mark-unmark 'vc-dir-mark-file)) | ||
| 1429 | |||
| 1430 | (defun vc-dir-mark-all-files (arg) | ||
| 1431 | "Mark all files with the same state as the current one. | ||
| 1432 | With a prefix argument mark all files. | ||
| 1433 | If the current entry is a directory, mark all child files. | ||
| 1434 | |||
| 1435 | The VC commands operate on files that are on the same state. | ||
| 1436 | This command is intended to make it easy to select all files that | ||
| 1437 | share the same state." | ||
| 1438 | (interactive "P") | ||
| 1439 | (if arg | ||
| 1440 | ;; Mark all files. | ||
| 1441 | (progn | ||
| 1442 | ;; First check that no directory is marked, we can't mark | ||
| 1443 | ;; files in that case. | ||
| 1444 | (ewoc-map | ||
| 1445 | (lambda (filearg) | ||
| 1446 | (when (and (vc-dir-fileinfo->directory filearg) | ||
| 1447 | (vc-dir-fileinfo->directory filearg)) | ||
| 1448 | (error "Cannot mark all files, directory `%s' marked" | ||
| 1449 | (vc-dir-fileinfo->name filearg)))) | ||
| 1450 | vc-ewoc) | ||
| 1451 | (ewoc-map | ||
| 1452 | (lambda (filearg) | ||
| 1453 | (unless (vc-dir-fileinfo->marked filearg) | ||
| 1454 | (setf (vc-dir-fileinfo->marked filearg) t) | ||
| 1455 | t)) | ||
| 1456 | vc-ewoc)) | ||
| 1457 | (let ((data (ewoc-data (ewoc-locate vc-ewoc)))) | ||
| 1458 | (if (vc-dir-fileinfo->directory data) | ||
| 1459 | ;; It's a directory, mark child files. | ||
| 1460 | (let ((crt (ewoc-locate vc-ewoc))) | ||
| 1461 | (unless (vc-dir-children-marked-p crt) | ||
| 1462 | (while (setq crt (ewoc-next vc-ewoc crt)) | ||
| 1463 | (let ((crt-data (ewoc-data crt))) | ||
| 1464 | (unless (vc-dir-fileinfo->directory crt-data) | ||
| 1465 | (setf (vc-dir-fileinfo->marked crt-data) t) | ||
| 1466 | (ewoc-invalidate vc-ewoc crt)))))) | ||
| 1467 | ;; It's a file | ||
| 1468 | (let ((state (vc-dir-fileinfo->state data)) | ||
| 1469 | (crt (ewoc-nth vc-ewoc 0))) | ||
| 1470 | (while crt | ||
| 1471 | (let ((crt-data (ewoc-data crt))) | ||
| 1472 | (when (and (not (vc-dir-fileinfo->marked crt-data)) | ||
| 1473 | (eq (vc-dir-fileinfo->state crt-data) state) | ||
| 1474 | (not (vc-dir-fileinfo->directory crt-data))) | ||
| 1475 | (vc-dir-mark-file crt))) | ||
| 1476 | (setq crt (ewoc-next vc-ewoc crt)))))))) | ||
| 1477 | |||
| 1478 | (defun vc-dir-unmark-file () | ||
| 1479 | ;; Unmark the current file and move to the next line. | ||
| 1480 | (let* ((crt (ewoc-locate vc-ewoc)) | ||
| 1481 | (file (ewoc-data crt))) | ||
| 1482 | (setf (vc-dir-fileinfo->marked file) nil) | ||
| 1483 | (ewoc-invalidate vc-ewoc crt) | ||
| 1484 | (unless (mouse-event-p last-command-event) | ||
| 1485 | (vc-dir-next-line 1)))) | ||
| 1486 | |||
| 1487 | (defun vc-dir-unmark () | ||
| 1488 | "Unmark the current file or all files in the region. | ||
| 1489 | If the region is active, unmark all the files in the region. | ||
| 1490 | Otherwise mark the file on the current line and move to the next | ||
| 1491 | line." | ||
| 1492 | (interactive) | ||
| 1493 | (vc-dir-mark-unmark 'vc-dir-unmark-file)) | ||
| 1494 | |||
| 1495 | (defun vc-dir-unmark-file-up () | ||
| 1496 | "Move to the previous line and unmark the file." | ||
| 1497 | (interactive) | ||
| 1498 | ;; If we're on the first line, we won't move up, but we will still | ||
| 1499 | ;; remove the mark. This seems a bit odd but it is what buffer-menu | ||
| 1500 | ;; does. | ||
| 1501 | (let* ((prev (ewoc-goto-prev vc-ewoc 1)) | ||
| 1502 | (file (ewoc-data prev))) | ||
| 1503 | (setf (vc-dir-fileinfo->marked file) nil) | ||
| 1504 | (ewoc-invalidate vc-ewoc prev) | ||
| 1505 | (vc-dir-move-to-goal-column))) | ||
| 1506 | |||
| 1507 | (defun vc-dir-unmark-all-files (arg) | ||
| 1508 | "Unmark all files with the same state as the current one. | ||
| 1509 | With a prefix argument unmark all files. | ||
| 1510 | If the current entry is a directory, unmark all the child files. | ||
| 1511 | |||
| 1512 | The VC commands operate on files that are on the same state. | ||
| 1513 | This command is intended to make it easy to deselect all files | ||
| 1514 | that share the same state." | ||
| 1515 | (interactive "P") | ||
| 1516 | (if arg | ||
| 1517 | (ewoc-map | ||
| 1518 | (lambda (filearg) | ||
| 1519 | (when (vc-dir-fileinfo->marked filearg) | ||
| 1520 | (setf (vc-dir-fileinfo->marked filearg) nil) | ||
| 1521 | t)) | ||
| 1522 | vc-ewoc) | ||
| 1523 | (let* ((crt (ewoc-locate vc-ewoc)) | ||
| 1524 | (data (ewoc-data crt))) | ||
| 1525 | (if (vc-dir-fileinfo->directory data) | ||
| 1526 | ;; It's a directory, unmark child files. | ||
| 1527 | (while (setq crt (ewoc-next vc-ewoc crt)) | ||
| 1528 | (let ((crt-data (ewoc-data crt))) | ||
| 1529 | (unless (vc-dir-fileinfo->directory crt-data) | ||
| 1530 | (setf (vc-dir-fileinfo->marked crt-data) nil) | ||
| 1531 | (ewoc-invalidate vc-ewoc crt)))) | ||
| 1532 | ;; It's a file | ||
| 1533 | (let ((crt-state (vc-dir-fileinfo->state (ewoc-data crt)))) | ||
| 1534 | (ewoc-map | ||
| 1535 | (lambda (filearg) | ||
| 1536 | (when (and (vc-dir-fileinfo->marked filearg) | ||
| 1537 | (eq (vc-dir-fileinfo->state filearg) crt-state)) | ||
| 1538 | (setf (vc-dir-fileinfo->marked filearg) nil) | ||
| 1539 | t)) | ||
| 1540 | vc-ewoc)))))) | ||
| 1541 | |||
| 1542 | (defun vc-dir-toggle-mark-file () | ||
| 1543 | (let* ((crt (ewoc-locate vc-ewoc)) | ||
| 1544 | (file (ewoc-data crt))) | ||
| 1545 | (if (vc-dir-fileinfo->marked file) | ||
| 1546 | (vc-dir-unmark-file) | ||
| 1547 | (vc-dir-mark-file)))) | ||
| 1548 | |||
| 1549 | (defun vc-dir-toggle-mark (e) | ||
| 1550 | (interactive "e") | ||
| 1551 | (vc-at-event e (vc-dir-mark-unmark 'vc-dir-toggle-mark-file))) | ||
| 1552 | |||
| 1553 | (defun vc-dir-register () | ||
| 1554 | "Register the marked files, or the current file if no marks." | ||
| 1555 | (interactive) | ||
| 1556 | ;; FIXME: Just pass the fileset to vc-register. | ||
| 1557 | (mapc (lambda (arg) (vc-register nil arg)) | ||
| 1558 | (or (vc-dir-marked-files) (list (vc-dir-current-file))))) | ||
| 1559 | |||
| 1560 | (defun vc-dir-delete-file () | ||
| 1561 | "Delete the marked files, or the current file if no marks." | ||
| 1562 | (interactive) | ||
| 1563 | (mapc 'vc-delete-file (or (vc-dir-marked-files) | ||
| 1564 | (list (vc-dir-current-file))))) | ||
| 1565 | |||
| 1566 | (defun vc-dir-show-fileentry (file) | ||
| 1567 | "Insert an entry for a specific file into the current VC status listing. | ||
| 1568 | This is typically used if the file is up-to-date (or has been added | ||
| 1569 | outside of VC) and one wants to do some operation on it." | ||
| 1570 | (interactive "fShow file: ") | ||
| 1571 | (vc-dir-update (list (list (file-relative-name file) (vc-state file))) (current-buffer))) | ||
| 1572 | |||
| 1573 | (defun vc-dir-find-file () | ||
| 1574 | "Find the file on the current line." | ||
| 1575 | (interactive) | ||
| 1576 | (find-file (vc-dir-current-file))) | ||
| 1577 | |||
| 1578 | (defun vc-dir-find-file-other-window () | ||
| 1579 | "Find the file on the current line, in another window." | ||
| 1580 | (interactive) | ||
| 1581 | (find-file-other-window (vc-dir-current-file))) | ||
| 1582 | |||
| 1583 | (defun vc-dir-current-file () | ||
| 1584 | (let ((node (ewoc-locate vc-ewoc))) | ||
| 1585 | (unless node | ||
| 1586 | (error "No file available.")) | ||
| 1587 | (expand-file-name (vc-dir-fileinfo->name (ewoc-data node))))) | ||
| 1588 | |||
| 1589 | (defun vc-dir-marked-files () | ||
| 1590 | "Return the list of marked files." | ||
| 1591 | (mapcar | ||
| 1592 | (lambda (elem) (expand-file-name (vc-dir-fileinfo->name elem))) | ||
| 1593 | (ewoc-collect vc-ewoc 'vc-dir-fileinfo->marked))) | ||
| 1594 | |||
| 1595 | (defun vc-dir-marked-only-files () | ||
| 1596 | "Return the list of marked files, for marked directories, return child files." | ||
| 1597 | |||
| 1598 | (let ((crt (ewoc-nth vc-ewoc 0)) | ||
| 1599 | result) | ||
| 1600 | (while crt | ||
| 1601 | (let ((crt-data (ewoc-data crt))) | ||
| 1602 | (if (vc-dir-fileinfo->marked crt-data) | ||
| 1603 | (if (vc-dir-fileinfo->directory crt-data) | ||
| 1604 | (let* ((dir (vc-dir-fileinfo->directory crt-data)) | ||
| 1605 | (dirlen (length dir)) | ||
| 1606 | data) | ||
| 1607 | (while | ||
| 1608 | (and (setq crt (ewoc-next vc-ewoc crt)) | ||
| 1609 | (string-equal | ||
| 1610 | (substring | ||
| 1611 | (progn | ||
| 1612 | (setq data (ewoc-data crt)) | ||
| 1613 | (let ((crtdir (vc-dir-fileinfo->directory data))) | ||
| 1614 | (if crtdir | ||
| 1615 | crtdir | ||
| 1616 | (file-name-directory | ||
| 1617 | (expand-file-name | ||
| 1618 | (vc-dir-fileinfo->name data)))))) | ||
| 1619 | 0 dirlen) | ||
| 1620 | dir)) | ||
| 1621 | (unless (vc-dir-fileinfo->directory data) | ||
| 1622 | (push (vc-dir-fileinfo->name data) result)))) | ||
| 1623 | (push (expand-file-name (vc-dir-fileinfo->name crt-data)) result) | ||
| 1624 | (setq crt (ewoc-next vc-ewoc crt))) | ||
| 1625 | (setq crt (ewoc-next vc-ewoc crt))))) | ||
| 1626 | result)) | ||
| 1627 | |||
| 1628 | (defun vc-dir-hide-up-to-date () | ||
| 1629 | "Hide up-to-date items from display." | ||
| 1630 | (interactive) | ||
| 1631 | (ewoc-filter | ||
| 1632 | vc-ewoc | ||
| 1633 | (lambda (crt) (not (eq (vc-dir-fileinfo->state crt) 'up-to-date))))) | ||
| 1634 | |||
| 1635 | ;; FIXME: VCS-specific concept of backend needs to be factored out | ||
| 1636 | (defun vc-default-status-fileinfo-extra (backend file) | ||
| 1637 | nil) | ||
| 1638 | |||
| 1639 | (defun vc-dir-mark-buffer-changed (&optional fname) | ||
| 1640 | (let* ((file (or fname (expand-file-name buffer-file-name))) | ||
| 1641 | (found-vc-dir-buf nil)) | ||
| 1642 | (save-excursion | ||
| 1643 | (dolist (status-buf (buffer-list)) | ||
| 1644 | (set-buffer status-buf) | ||
| 1645 | ;; look for a vc-dir buffer that might show this file. | ||
| 1646 | (when (eq major-mode 'vc-dir-mode) | ||
| 1647 | (setq found-vc-dir-buf t) | ||
| 1648 | (let ((ddir (expand-file-name default-directory))) | ||
| 1649 | ;; FIXME: VCS-specific stuff needs to be factored out. | ||
| 1650 | ;; This test is cvs-string-prefix-p | ||
| 1651 | (when (eq t (compare-strings file nil (length ddir) ddir nil nil)) | ||
| 1652 | (let* | ||
| 1653 | ((file-short (substring file (length ddir))) | ||
| 1654 | (backend (vc-backend file)) | ||
| 1655 | (state (and backend (vc-state file))) | ||
| 1656 | (extra | ||
| 1657 | (and backend | ||
| 1658 | (vc-call-backend backend 'status-fileinfo-extra file))) | ||
| 1659 | (entry | ||
| 1660 | (list file-short (if state state 'unregistered) extra))) | ||
| 1661 | (vc-dir-update (list entry) status-buf)))))) | ||
| 1662 | ;; We didn't find any vc-dir buffers, remove the hook, it is | ||
| 1663 | ;; not needed. | ||
| 1664 | (unless found-vc-dir-buf (remove-hook 'after-save-hook 'vc-dir-mark-buffer-changed))))) | ||
| 1665 | |||
| 1666 | ;; These things should probably be generally available | ||
| 1667 | |||
| 1668 | (defun vc-file-tree-walk (dirname func &rest args) | ||
| 1669 | "Walk recursively through DIRNAME. | ||
| 1670 | Invoke FUNC f ARGS on each VC-managed file f underneath it." | ||
| 1671 | (vc-file-tree-walk-internal (expand-file-name dirname) func args) | ||
| 1672 | (message "Traversing directory %s...done" dirname)) | ||
| 1673 | |||
| 1674 | (defun vc-file-tree-walk-internal (file func args) | ||
| 1675 | (if (not (file-directory-p file)) | ||
| 1676 | (when (vc-backend file) (apply func file args)) | ||
| 1677 | (message "Traversing directory %s..." (abbreviate-file-name file)) | ||
| 1678 | (let ((dir (file-name-as-directory file))) | ||
| 1679 | (mapcar | ||
| 1680 | (lambda (f) (or | ||
| 1681 | (string-equal f ".") | ||
| 1682 | (string-equal f "..") | ||
| 1683 | (member f vc-directory-exclusion-list) | ||
| 1684 | (let ((dirf (expand-file-name f dir))) | ||
| 1685 | (or | ||
| 1686 | (file-symlink-p dirf) ;; Avoid possible loops. | ||
| 1687 | (vc-file-tree-walk-internal dirf func args))))) | ||
| 1688 | (directory-files dir))))) | ||
diff --git a/lisp/vc.el b/lisp/vc.el index 283f714e935..e12af0cec90 100644 --- a/lisp/vc.el +++ b/lisp/vc.el | |||
| @@ -693,6 +693,11 @@ | |||
| 693 | (require 'tool-bar) | 693 | (require 'tool-bar) |
| 694 | (require 'ewoc) | 694 | (require 'ewoc) |
| 695 | 695 | ||
| 696 | (eval-when-compile | ||
| 697 | (require 'cl) | ||
| 698 | (require 'dired) ; for dired-map-over-marks macro | ||
| 699 | (require 'dired-aux)) ; for dired-kill-{line,tree} | ||
| 700 | |||
| 696 | (unless (assoc 'vc-parent-buffer minor-mode-alist) | 701 | (unless (assoc 'vc-parent-buffer minor-mode-alist) |
| 697 | (setq minor-mode-alist | 702 | (setq minor-mode-alist |
| 698 | (cons '(vc-parent-buffer vc-parent-buffer-name) | 703 | (cons '(vc-parent-buffer vc-parent-buffer-name) |
| @@ -709,6 +714,14 @@ | |||
| 709 | :type 'boolean | 714 | :type 'boolean |
| 710 | :group 'vc) | 715 | :group 'vc) |
| 711 | 716 | ||
| 717 | (defcustom vc-delete-logbuf-window t | ||
| 718 | "If non-nil, delete the *VC-log* buffer and window after each logical action. | ||
| 719 | If nil, bury that buffer instead. | ||
| 720 | This is most useful if you have multiple windows on a frame and would like to | ||
| 721 | preserve the setting." | ||
| 722 | :type 'boolean | ||
| 723 | :group 'vc) | ||
| 724 | |||
| 712 | (defcustom vc-initial-comment nil | 725 | (defcustom vc-initial-comment nil |
| 713 | "If non-nil, prompt for initial comment when a file is registered." | 726 | "If non-nil, prompt for initial comment when a file is registered." |
| 714 | :type 'boolean | 727 | :type 'boolean |
| @@ -722,6 +735,11 @@ can also be overridden by a particular VC backend." | |||
| 722 | :group 'vc | 735 | :group 'vc |
| 723 | :version "20.3") | 736 | :version "20.3") |
| 724 | 737 | ||
| 738 | (defcustom vc-command-messages nil | ||
| 739 | "If non-nil, display run messages from back-end commands." | ||
| 740 | :type 'boolean | ||
| 741 | :group 'vc) | ||
| 742 | |||
| 725 | (defcustom vc-checkin-switches nil | 743 | (defcustom vc-checkin-switches nil |
| 726 | "A string or list of strings specifying extra switches for checkin. | 744 | "A string or list of strings specifying extra switches for checkin. |
| 727 | These are passed to the checkin program by \\[vc-checkin]." | 745 | These are passed to the checkin program by \\[vc-checkin]." |
| @@ -752,6 +770,24 @@ These are passed to the checkin program by \\[vc-register]." | |||
| 752 | string)) | 770 | string)) |
| 753 | :group 'vc) | 771 | :group 'vc) |
| 754 | 772 | ||
| 773 | (defcustom vc-dired-listing-switches "-al" | ||
| 774 | "Switches passed to `ls' for vc-dired. MUST contain the `l' option." | ||
| 775 | :type 'string | ||
| 776 | :group 'vc | ||
| 777 | :version "21.1") | ||
| 778 | |||
| 779 | (defcustom vc-dired-recurse t | ||
| 780 | "If non-nil, show directory trees recursively in VC Dired." | ||
| 781 | :type 'boolean | ||
| 782 | :group 'vc | ||
| 783 | :version "20.3") | ||
| 784 | |||
| 785 | (defcustom vc-dired-terse-display t | ||
| 786 | "If non-nil, show only locked or locally modified files in VC Dired." | ||
| 787 | :type 'boolean | ||
| 788 | :group 'vc | ||
| 789 | :version "20.3") | ||
| 790 | |||
| 755 | (defcustom vc-diff-switches nil | 791 | (defcustom vc-diff-switches nil |
| 756 | "A string or list of strings specifying switches for diff under VC. | 792 | "A string or list of strings specifying switches for diff under VC. |
| 757 | When running diff under a given BACKEND, VC concatenates the values of | 793 | When running diff under a given BACKEND, VC concatenates the values of |
| @@ -822,6 +858,12 @@ version control backend imposes itself." | |||
| 822 | :type 'hook | 858 | :type 'hook |
| 823 | :group 'vc) | 859 | :group 'vc) |
| 824 | 860 | ||
| 861 | (defcustom vc-dir-mode-hook nil | ||
| 862 | "Normal hook run by `vc-dir-mode'. | ||
| 863 | See `run-hooks'." | ||
| 864 | :type 'hook | ||
| 865 | :group 'vc) | ||
| 866 | |||
| 825 | ;; Annotate customization | 867 | ;; Annotate customization |
| 826 | (defcustom vc-annotate-color-map | 868 | (defcustom vc-annotate-color-map |
| 827 | (if (and (tty-display-color-p) (<= (display-color-cells) 8)) | 869 | (if (and (tty-display-color-p) (<= (display-color-cells) 8)) |
| @@ -965,8 +1007,12 @@ and that its contents match what the master file says." | |||
| 965 | Backends that offer asynchronous diffs should respect this variable | 1007 | Backends that offer asynchronous diffs should respect this variable |
| 966 | in their implementation of vc-BACKEND-diff.") | 1008 | in their implementation of vc-BACKEND-diff.") |
| 967 | 1009 | ||
| 1010 | (defvar vc-log-fileset) | ||
| 968 | (defvar vc-log-revision) | 1011 | (defvar vc-log-revision) |
| 969 | 1012 | ||
| 1013 | (defvar vc-dired-mode nil) | ||
| 1014 | (make-variable-buffer-local 'vc-dired-mode) | ||
| 1015 | |||
| 970 | ;; File property caching | 1016 | ;; File property caching |
| 971 | 1017 | ||
| 972 | (defun vc-clear-context () | 1018 | (defun vc-clear-context () |
| @@ -1028,6 +1074,121 @@ However, before executing BODY, find FILE, and after BODY, save buffer." | |||
| 1028 | ,@body | 1074 | ,@body |
| 1029 | (save-buffer))))) | 1075 | (save-buffer))))) |
| 1030 | 1076 | ||
| 1077 | (defun vc-position-context (posn) | ||
| 1078 | "Save a bit of the text around POSN in the current buffer. | ||
| 1079 | Used to help us find the corresponding position again later | ||
| 1080 | if markers are destroyed or corrupted." | ||
| 1081 | ;; A lot of this was shamelessly lifted from Sebastian Kremer's | ||
| 1082 | ;; rcs.el mode. | ||
| 1083 | (list posn | ||
| 1084 | (buffer-size) | ||
| 1085 | (buffer-substring posn | ||
| 1086 | (min (point-max) (+ posn 100))))) | ||
| 1087 | |||
| 1088 | (defun vc-find-position-by-context (context) | ||
| 1089 | "Return the position of CONTEXT in the current buffer. | ||
| 1090 | If CONTEXT cannot be found, return nil." | ||
| 1091 | (let ((context-string (nth 2 context))) | ||
| 1092 | (if (equal "" context-string) | ||
| 1093 | (point-max) | ||
| 1094 | (save-excursion | ||
| 1095 | (let ((diff (- (nth 1 context) (buffer-size)))) | ||
| 1096 | (when (< diff 0) (setq diff (- diff))) | ||
| 1097 | (goto-char (nth 0 context)) | ||
| 1098 | (if (or (search-forward context-string nil t) | ||
| 1099 | ;; Can't use search-backward since the match may continue | ||
| 1100 | ;; after point. | ||
| 1101 | (progn (goto-char (- (point) diff (length context-string))) | ||
| 1102 | ;; goto-char doesn't signal an error at | ||
| 1103 | ;; beginning of buffer like backward-char would | ||
| 1104 | (search-forward context-string nil t))) | ||
| 1105 | ;; to beginning of OSTRING | ||
| 1106 | (- (point) (length context-string)))))))) | ||
| 1107 | |||
| 1108 | (defun vc-context-matches-p (posn context) | ||
| 1109 | "Return t if POSN matches CONTEXT, nil otherwise." | ||
| 1110 | (let* ((context-string (nth 2 context)) | ||
| 1111 | (len (length context-string)) | ||
| 1112 | (end (+ posn len))) | ||
| 1113 | (if (> end (1+ (buffer-size))) | ||
| 1114 | nil | ||
| 1115 | (string= context-string (buffer-substring posn end))))) | ||
| 1116 | |||
| 1117 | (defun vc-buffer-context () | ||
| 1118 | "Return a list (POINT-CONTEXT MARK-CONTEXT REPARSE). | ||
| 1119 | Used by `vc-restore-buffer-context' to later restore the context." | ||
| 1120 | (let ((point-context (vc-position-context (point))) | ||
| 1121 | ;; Use mark-marker to avoid confusion in transient-mark-mode. | ||
| 1122 | (mark-context (when (eq (marker-buffer (mark-marker)) (current-buffer)) | ||
| 1123 | (vc-position-context (mark-marker)))) | ||
| 1124 | ;; Make the right thing happen in transient-mark-mode. | ||
| 1125 | (mark-active nil) | ||
| 1126 | ;; The new compilation code does not use compilation-error-list any | ||
| 1127 | ;; more, so the code below is now ineffective and might as well | ||
| 1128 | ;; be disabled. -- Stef | ||
| 1129 | ;; ;; We may want to reparse the compilation buffer after revert | ||
| 1130 | ;; (reparse (and (boundp 'compilation-error-list) ;compile loaded | ||
| 1131 | ;; ;; Construct a list; each elt is nil or a buffer | ||
| 1132 | ;; ;; if that buffer is a compilation output buffer | ||
| 1133 | ;; ;; that contains markers into the current buffer. | ||
| 1134 | ;; (save-current-buffer | ||
| 1135 | ;; (mapcar (lambda (buffer) | ||
| 1136 | ;; (set-buffer buffer) | ||
| 1137 | ;; (let ((errors (or | ||
| 1138 | ;; compilation-old-error-list | ||
| 1139 | ;; compilation-error-list)) | ||
| 1140 | ;; (buffer-error-marked-p nil)) | ||
| 1141 | ;; (while (and (consp errors) | ||
| 1142 | ;; (not buffer-error-marked-p)) | ||
| 1143 | ;; (and (markerp (cdr (car errors))) | ||
| 1144 | ;; (eq buffer | ||
| 1145 | ;; (marker-buffer | ||
| 1146 | ;; (cdr (car errors)))) | ||
| 1147 | ;; (setq buffer-error-marked-p t)) | ||
| 1148 | ;; (setq errors (cdr errors))) | ||
| 1149 | ;; (if buffer-error-marked-p buffer))) | ||
| 1150 | ;; (buffer-list))))) | ||
| 1151 | (reparse nil)) | ||
| 1152 | (list point-context mark-context reparse))) | ||
| 1153 | |||
| 1154 | (defun vc-restore-buffer-context (context) | ||
| 1155 | "Restore point/mark, and reparse any affected compilation buffers. | ||
| 1156 | CONTEXT is that which `vc-buffer-context' returns." | ||
| 1157 | (let ((point-context (nth 0 context)) | ||
| 1158 | (mark-context (nth 1 context)) | ||
| 1159 | ;; (reparse (nth 2 context)) | ||
| 1160 | ) | ||
| 1161 | ;; The new compilation code does not use compilation-error-list any | ||
| 1162 | ;; more, so the code below is now ineffective and might as well | ||
| 1163 | ;; be disabled. -- Stef | ||
| 1164 | ;; ;; Reparse affected compilation buffers. | ||
| 1165 | ;; (while reparse | ||
| 1166 | ;; (if (car reparse) | ||
| 1167 | ;; (with-current-buffer (car reparse) | ||
| 1168 | ;; (let ((compilation-last-buffer (current-buffer)) ;select buffer | ||
| 1169 | ;; ;; Record the position in the compilation buffer of | ||
| 1170 | ;; ;; the last error next-error went to. | ||
| 1171 | ;; (error-pos (marker-position | ||
| 1172 | ;; (car (car-safe compilation-error-list))))) | ||
| 1173 | ;; ;; Reparse the error messages as far as they were parsed before. | ||
| 1174 | ;; (compile-reinitialize-errors '(4) compilation-parsing-end) | ||
| 1175 | ;; ;; Move the pointer up to find the error we were at before | ||
| 1176 | ;; ;; reparsing. Now next-error should properly go to the next one. | ||
| 1177 | ;; (while (and compilation-error-list | ||
| 1178 | ;; (/= error-pos (car (car compilation-error-list)))) | ||
| 1179 | ;; (setq compilation-error-list (cdr compilation-error-list)))))) | ||
| 1180 | ;; (setq reparse (cdr reparse))) | ||
| 1181 | |||
| 1182 | ;; if necessary, restore point and mark | ||
| 1183 | (if (not (vc-context-matches-p (point) point-context)) | ||
| 1184 | (let ((new-point (vc-find-position-by-context point-context))) | ||
| 1185 | (when new-point (goto-char new-point)))) | ||
| 1186 | (and mark-active | ||
| 1187 | mark-context | ||
| 1188 | (not (vc-context-matches-p (mark) mark-context)) | ||
| 1189 | (let ((new-mark (vc-find-position-by-context mark-context))) | ||
| 1190 | (when new-mark (set-mark new-mark)))))) | ||
| 1191 | |||
| 1031 | ;;; Code for deducing what fileset and backend to assume | 1192 | ;;; Code for deducing what fileset and backend to assume |
| 1032 | 1193 | ||
| 1033 | (defun vc-responsible-backend (file &optional register) | 1194 | (defun vc-responsible-backend (file &optional register) |
| @@ -1205,6 +1366,8 @@ NOT-URGENT means it is ok to continue if the user says not to save." | |||
| 1205 | (unless not-urgent | 1366 | (unless not-urgent |
| 1206 | (error "Aborted"))))) | 1367 | (error "Aborted"))))) |
| 1207 | 1368 | ||
| 1369 | (defvar vc-dired-window-configuration) | ||
| 1370 | |||
| 1208 | (defun vc-compatible-state (p q) | 1371 | (defun vc-compatible-state (p q) |
| 1209 | "Controls which states can be in the same commit." | 1372 | "Controls which states can be in the same commit." |
| 1210 | (or | 1373 | (or |
| @@ -1536,6 +1699,51 @@ rather than user editing!" | |||
| 1536 | (let ((buffer (get-file-buffer file))) | 1699 | (let ((buffer (get-file-buffer file))) |
| 1537 | (vc-dir-mark-buffer-changed file)))) | 1700 | (vc-dir-mark-buffer-changed file)))) |
| 1538 | 1701 | ||
| 1702 | |||
| 1703 | (defun vc-start-entry (files rev comment initial-contents msg action &optional after-hook) | ||
| 1704 | "Accept a comment for an operation on FILES revision REV. | ||
| 1705 | If COMMENT is nil, pop up a VC-log buffer, emit MSG, and set the | ||
| 1706 | action on close to ACTION. If COMMENT is a string and | ||
| 1707 | INITIAL-CONTENTS is non-nil, then COMMENT is used as the initial | ||
| 1708 | contents of the log entry buffer. If COMMENT is a string and | ||
| 1709 | INITIAL-CONTENTS is nil, do action immediately as if the user had | ||
| 1710 | entered COMMENT. If COMMENT is t, also do action immediately with an | ||
| 1711 | empty comment. Remember the file's buffer in `vc-parent-buffer' | ||
| 1712 | \(current one if no file). AFTER-HOOK specifies the local value | ||
| 1713 | for `vc-log-after-operation-hook'." | ||
| 1714 | (let ((parent | ||
| 1715 | (if (or (eq major-mode 'vc-dired-mode) (eq major-mode 'vc-dir-mode)) | ||
| 1716 | ;; If we are called from VC dired, the parent buffer is | ||
| 1717 | ;; the current buffer. | ||
| 1718 | (current-buffer) | ||
| 1719 | (if (and files (equal (length files) 1)) | ||
| 1720 | (get-file-buffer (car files)) | ||
| 1721 | (current-buffer))))) | ||
| 1722 | (when vc-before-checkin-hook | ||
| 1723 | (if files | ||
| 1724 | (with-current-buffer parent | ||
| 1725 | (run-hooks 'vc-before-checkin-hook)) | ||
| 1726 | (run-hooks 'vc-before-checkin-hook))) | ||
| 1727 | (if (and comment (not initial-contents)) | ||
| 1728 | (set-buffer (get-buffer-create "*VC-log*")) | ||
| 1729 | (pop-to-buffer (get-buffer-create "*VC-log*"))) | ||
| 1730 | (set (make-local-variable 'vc-parent-buffer) parent) | ||
| 1731 | (set (make-local-variable 'vc-parent-buffer-name) | ||
| 1732 | (concat " from " (buffer-name vc-parent-buffer))) | ||
| 1733 | ;;(if file (vc-mode-line file)) | ||
| 1734 | (vc-log-edit files) | ||
| 1735 | (make-local-variable 'vc-log-after-operation-hook) | ||
| 1736 | (when after-hook | ||
| 1737 | (setq vc-log-after-operation-hook after-hook)) | ||
| 1738 | (setq vc-log-operation action) | ||
| 1739 | (setq vc-log-revision rev) | ||
| 1740 | (when comment | ||
| 1741 | (erase-buffer) | ||
| 1742 | (when (stringp comment) (insert comment))) | ||
| 1743 | (if (or (not comment) initial-contents) | ||
| 1744 | (message "%s Type C-c C-c when done" msg) | ||
| 1745 | (vc-finish-logentry (eq comment t))))) | ||
| 1746 | |||
| 1539 | (defun vc-checkout (file &optional writable rev) | 1747 | (defun vc-checkout (file &optional writable rev) |
| 1540 | "Retrieve a copy of the revision REV of FILE. | 1748 | "Retrieve a copy of the revision REV of FILE. |
| 1541 | If WRITABLE is non-nil, make sure the retrieved file is writable. | 1749 | If WRITABLE is non-nil, make sure the retrieved file is writable. |
| @@ -1635,6 +1843,57 @@ Runs the normal hook `vc-checkin-hook'." | |||
| 1635 | (message "Checking in %s...done" (vc-delistify files))) | 1843 | (message "Checking in %s...done" (vc-delistify files))) |
| 1636 | 'vc-checkin-hook)) | 1844 | 'vc-checkin-hook)) |
| 1637 | 1845 | ||
| 1846 | (defun vc-finish-logentry (&optional nocomment) | ||
| 1847 | "Complete the operation implied by the current log entry. | ||
| 1848 | Use the contents of the current buffer as a check-in or registration | ||
| 1849 | comment. If the optional arg NOCOMMENT is non-nil, then don't check | ||
| 1850 | the buffer contents as a comment." | ||
| 1851 | (interactive) | ||
| 1852 | ;; Check and record the comment, if any. | ||
| 1853 | (unless nocomment | ||
| 1854 | (run-hooks 'vc-logentry-check-hook)) | ||
| 1855 | ;; Sync parent buffer in case the user modified it while editing the comment. | ||
| 1856 | ;; But not if it is a vc-dired buffer. | ||
| 1857 | (with-current-buffer vc-parent-buffer | ||
| 1858 | (or vc-dired-mode (eq major-mode 'vc-dir-mode) (vc-buffer-sync))) | ||
| 1859 | (unless vc-log-operation | ||
| 1860 | (error "No log operation is pending")) | ||
| 1861 | ;; save the parameters held in buffer-local variables | ||
| 1862 | (let ((log-operation vc-log-operation) | ||
| 1863 | (log-fileset vc-log-fileset) | ||
| 1864 | (log-revision vc-log-revision) | ||
| 1865 | (log-entry (buffer-string)) | ||
| 1866 | (after-hook vc-log-after-operation-hook) | ||
| 1867 | (tmp-vc-parent-buffer vc-parent-buffer)) | ||
| 1868 | (pop-to-buffer vc-parent-buffer) | ||
| 1869 | ;; OK, do it to it | ||
| 1870 | (save-excursion | ||
| 1871 | (funcall log-operation | ||
| 1872 | log-fileset | ||
| 1873 | log-revision | ||
| 1874 | log-entry)) | ||
| 1875 | ;; Remove checkin window (after the checkin so that if that fails | ||
| 1876 | ;; we don't zap the *VC-log* buffer and the typing therein). | ||
| 1877 | ;; -- IMO this should be replaced with quit-window | ||
| 1878 | (let ((logbuf (get-buffer "*VC-log*"))) | ||
| 1879 | (cond ((and logbuf vc-delete-logbuf-window) | ||
| 1880 | (delete-windows-on logbuf (selected-frame)) | ||
| 1881 | ;; Kill buffer and delete any other dedicated windows/frames. | ||
| 1882 | (kill-buffer logbuf)) | ||
| 1883 | (logbuf (pop-to-buffer "*VC-log*") | ||
| 1884 | (bury-buffer) | ||
| 1885 | (pop-to-buffer tmp-vc-parent-buffer)))) | ||
| 1886 | ;; Now make sure we see the expanded headers | ||
| 1887 | (when log-fileset | ||
| 1888 | (mapc | ||
| 1889 | (lambda (file) (vc-resynch-buffer file vc-keep-workfiles t)) | ||
| 1890 | log-fileset)) | ||
| 1891 | (when vc-dired-mode | ||
| 1892 | (dired-move-to-filename)) | ||
| 1893 | (when (eq major-mode 'vc-dir-mode) | ||
| 1894 | (vc-dir-move-to-goal-column)) | ||
| 1895 | (run-hooks after-hook 'vc-finish-logentry-hook))) | ||
| 1896 | |||
| 1638 | ;;; Additional entry points for examining version histories | 1897 | ;;; Additional entry points for examining version histories |
| 1639 | 1898 | ||
| 1640 | ;; (defun vc-default-diff-tree (backend dir rev1 rev2) | 1899 | ;; (defun vc-default-diff-tree (backend dir rev1 rev2) |
| @@ -2020,7 +2279,140 @@ See Info node `Merging'." | |||
| 2020 | ;;;###autoload | 2279 | ;;;###autoload |
| 2021 | (defalias 'vc-resolve-conflicts 'smerge-ediff) | 2280 | (defalias 'vc-resolve-conflicts 'smerge-ediff) |
| 2022 | 2281 | ||
| 2023 | ;; Hooks to the dired code | 2282 | ;; The VC directory major mode. Coopt Dired for this. |
| 2283 | ;; All VC commands get mapped into logical equivalents. | ||
| 2284 | |||
| 2285 | (defvar vc-dired-switches) | ||
| 2286 | (defvar vc-dired-terse-mode) | ||
| 2287 | |||
| 2288 | (defvar vc-dired-mode-map | ||
| 2289 | (let ((map (make-sparse-keymap)) | ||
| 2290 | (vmap (make-sparse-keymap))) | ||
| 2291 | (define-key map "\C-xv" vmap) | ||
| 2292 | (define-key map "v" vmap) | ||
| 2293 | (set-keymap-parent vmap vc-prefix-map) | ||
| 2294 | (define-key vmap "t" 'vc-dired-toggle-terse-mode) | ||
| 2295 | map)) | ||
| 2296 | |||
| 2297 | (define-derived-mode vc-dired-mode dired-mode "Dired under " | ||
| 2298 | "The major mode used in VC directory buffers. | ||
| 2299 | |||
| 2300 | It works like Dired, but lists only files under version control, with | ||
| 2301 | the current VC state of each file being indicated in the place of the | ||
| 2302 | file's link count, owner, group and size. Subdirectories are also | ||
| 2303 | listed, and you may insert them into the buffer as desired, like in | ||
| 2304 | Dired. | ||
| 2305 | |||
| 2306 | All Dired commands operate normally, with the exception of `v', which | ||
| 2307 | is redefined as the version control prefix, so that you can type | ||
| 2308 | `vl', `v=' etc. to invoke `vc-print-log', `vc-diff', and the like on | ||
| 2309 | the file named in the current Dired buffer line. `vv' invokes | ||
| 2310 | `vc-next-action' on this file, or on all files currently marked. | ||
| 2311 | There is a special command, `*l', to mark all files currently locked." | ||
| 2312 | ;; define-derived-mode does it for us in Emacs-21, but not in Emacs-20. | ||
| 2313 | ;; We do it here because dired might not be loaded yet | ||
| 2314 | ;; when vc-dired-mode-map is initialized. | ||
| 2315 | (set-keymap-parent vc-dired-mode-map dired-mode-map) | ||
| 2316 | (add-hook 'dired-after-readin-hook 'vc-dired-hook nil t) | ||
| 2317 | ;; The following is slightly modified from files.el, | ||
| 2318 | ;; because file lines look a bit different in vc-dired-mode | ||
| 2319 | ;; (the column before the date does not end in a digit). | ||
| 2320 | ;; albinus: It should be done in the original declaration. Problem | ||
| 2321 | ;; is the optional empty state-info; otherwise ")" would be good | ||
| 2322 | ;; enough as delimeter. | ||
| 2323 | (set (make-local-variable 'directory-listing-before-filename-regexp) | ||
| 2324 | (let* ((l "\\([A-Za-z]\\|[^\0-\177]\\)") | ||
| 2325 | ;; In some locales, month abbreviations are as short as 2 letters, | ||
| 2326 | ;; and they can be followed by ".". | ||
| 2327 | (month (concat l l "+\\.?")) | ||
| 2328 | (s " ") | ||
| 2329 | (yyyy "[0-9][0-9][0-9][0-9]") | ||
| 2330 | (dd "[ 0-3][0-9]") | ||
| 2331 | (HH:MM "[ 0-2][0-9]:[0-5][0-9]") | ||
| 2332 | (seconds "[0-6][0-9]\\([.,][0-9]+\\)?") | ||
| 2333 | (zone "[-+][0-2][0-9][0-5][0-9]") | ||
| 2334 | (iso-mm-dd "[01][0-9]-[0-3][0-9]") | ||
| 2335 | (iso-time (concat HH:MM "\\(:" seconds "\\( ?" zone "\\)?\\)?")) | ||
| 2336 | (iso (concat "\\(\\(" yyyy "-\\)?" iso-mm-dd "[ T]" iso-time | ||
| 2337 | "\\|" yyyy "-" iso-mm-dd "\\)")) | ||
| 2338 | (western (concat "\\(" month s "+" dd "\\|" dd "\\.?" s month "\\)" | ||
| 2339 | s "+" | ||
| 2340 | "\\(" HH:MM "\\|" yyyy "\\)")) | ||
| 2341 | (western-comma (concat month s "+" dd "," s "+" yyyy)) | ||
| 2342 | ;; Japanese MS-Windows ls-lisp has one-digit months, and | ||
| 2343 | ;; omits the Kanji characters after month and day-of-month. | ||
| 2344 | (mm "[ 0-1]?[0-9]") | ||
| 2345 | (japanese | ||
| 2346 | (concat mm l "?" s dd l "?" s "+" | ||
| 2347 | "\\(" HH:MM "\\|" yyyy l "?" "\\)"))) | ||
| 2348 | ;; the .* below ensures that we find the last match on a line | ||
| 2349 | (concat ".*" s | ||
| 2350 | "\\(" western "\\|" western-comma "\\|" japanese "\\|" iso "\\)" | ||
| 2351 | s "+"))) | ||
| 2352 | (and (boundp 'vc-dired-switches) | ||
| 2353 | vc-dired-switches | ||
| 2354 | (set (make-local-variable 'dired-actual-switches) | ||
| 2355 | vc-dired-switches)) | ||
| 2356 | (set (make-local-variable 'vc-dired-terse-mode) vc-dired-terse-display) | ||
| 2357 | (let ((backend-name (symbol-name (vc-responsible-backend | ||
| 2358 | default-directory)))) | ||
| 2359 | (setq mode-name (concat mode-name backend-name)) | ||
| 2360 | ;; Add menu after `vc-dired-mode-map' has `dired-mode-map' as the parent. | ||
| 2361 | (let ((vc-dire-menu-map (copy-keymap vc-menu-map))) | ||
| 2362 | (define-key-after (lookup-key vc-dired-mode-map [menu-bar]) [vc] | ||
| 2363 | (cons backend-name vc-dire-menu-map) 'subdir))) | ||
| 2364 | (setq vc-dired-mode t)) | ||
| 2365 | |||
| 2366 | (defun vc-dired-toggle-terse-mode () | ||
| 2367 | "Toggle terse display in VC Dired." | ||
| 2368 | (interactive) | ||
| 2369 | (if (not vc-dired-mode) | ||
| 2370 | nil | ||
| 2371 | (setq vc-dired-terse-mode (not vc-dired-terse-mode)) | ||
| 2372 | (if vc-dired-terse-mode | ||
| 2373 | (vc-dired-hook) | ||
| 2374 | (revert-buffer)))) | ||
| 2375 | |||
| 2376 | (defun vc-dired-mark-locked () | ||
| 2377 | "Mark all files currently locked." | ||
| 2378 | (interactive) | ||
| 2379 | (dired-mark-if (let ((f (dired-get-filename nil t))) | ||
| 2380 | (and f | ||
| 2381 | (not (file-directory-p f)) | ||
| 2382 | (not (vc-up-to-date-p f)))) | ||
| 2383 | "locked file")) | ||
| 2384 | |||
| 2385 | (define-key vc-dired-mode-map "*l" 'vc-dired-mark-locked) | ||
| 2386 | |||
| 2387 | (defun vc-dired-reformat-line (vc-info) | ||
| 2388 | "Reformat a directory-listing line. | ||
| 2389 | Replace various columns with version control information, VC-INFO. | ||
| 2390 | This code, like dired, assumes UNIX -l format." | ||
| 2391 | (beginning-of-line) | ||
| 2392 | (when (re-search-forward | ||
| 2393 | ;; Match link count, owner, group, size. Group may be missing, | ||
| 2394 | ;; and only the size is present in OS/2 -l format. | ||
| 2395 | "^..[drwxlts-]+ \\( *[0-9]+\\( [^ ]+ +\\([^ ]+ +\\)?[0-9]+\\)?\\) " | ||
| 2396 | (line-end-position) t) | ||
| 2397 | (replace-match (substring (concat vc-info " ") 0 10) | ||
| 2398 | t t nil 1))) | ||
| 2399 | |||
| 2400 | (defun vc-dired-ignorable-p (filename) | ||
| 2401 | "Should FILENAME be ignored in VC-Dired listings?" | ||
| 2402 | (catch t | ||
| 2403 | ;; Ignore anything that wouldn't be found by completion (.o, .la, etc.) | ||
| 2404 | (dolist (ignorable completion-ignored-extensions) | ||
| 2405 | (let ((ext (substring filename | ||
| 2406 | (- (length filename) | ||
| 2407 | (length ignorable))))) | ||
| 2408 | (if (string= ignorable ext) (throw t t)))) | ||
| 2409 | ;; Ignore Makefiles derived from something else | ||
| 2410 | (when (string= (file-name-nondirectory filename) "Makefile") | ||
| 2411 | (let* ((dir (file-name-directory filename)) | ||
| 2412 | (peers (directory-files (or dir default-directory)))) | ||
| 2413 | (if (or (member "Makefile.in" peers) (member "Makefile.am" peers)) | ||
| 2414 | (throw t t)))) | ||
| 2415 | nil)) | ||
| 2024 | 2416 | ||
| 2025 | (defun vc-dired-hook () | 2417 | (defun vc-dired-hook () |
| 2026 | "Reformat the listing according to version control. | 2418 | "Reformat the listing according to version control. |
| @@ -2098,6 +2490,980 @@ Called by dired after any portion of a vc-dired buffer has been read in." | |||
| 2098 | (goto-char (point-min)) | 2490 | (goto-char (point-min)) |
| 2099 | (message "No changes pending under %s" default-directory))))) | 2491 | (message "No changes pending under %s" default-directory))))) |
| 2100 | 2492 | ||
| 2493 | (defun vc-dired-purge () | ||
| 2494 | "Remove empty subdirs." | ||
| 2495 | (goto-char (point-min)) | ||
| 2496 | (while (dired-get-subdir) | ||
| 2497 | (forward-line 2) | ||
| 2498 | (if (dired-get-filename nil t) | ||
| 2499 | (if (not (dired-next-subdir 1 t)) | ||
| 2500 | (goto-char (point-max))) | ||
| 2501 | (forward-line -2) | ||
| 2502 | (if (not (string= (dired-current-directory) default-directory)) | ||
| 2503 | (dired-do-kill-lines t "") | ||
| 2504 | ;; We cannot remove the top level directory. | ||
| 2505 | ;; Just make it look a little nicer. | ||
| 2506 | (forward-line 1) | ||
| 2507 | (or (eobp) (kill-line)) | ||
| 2508 | (if (not (dired-next-subdir 1 t)) | ||
| 2509 | (goto-char (point-max)))))) | ||
| 2510 | (goto-char (point-min))) | ||
| 2511 | |||
| 2512 | (defun vc-dired-buffers-for-dir (dir) | ||
| 2513 | "Return a list of all vc-dired buffers that currently display DIR." | ||
| 2514 | (let (result) | ||
| 2515 | ;; Check whether dired is loaded. | ||
| 2516 | (when (fboundp 'dired-buffers-for-dir) | ||
| 2517 | (dolist (buffer (dired-buffers-for-dir dir)) | ||
| 2518 | (with-current-buffer buffer | ||
| 2519 | (when vc-dired-mode | ||
| 2520 | (push buffer result))))) | ||
| 2521 | (nreverse result))) | ||
| 2522 | |||
| 2523 | (defun vc-directory-resynch-file (file) | ||
| 2524 | "Update the entries for FILE in any VC Dired buffers that list it." | ||
| 2525 | ;;FIXME This needs to be implemented so it works for vc-dir | ||
| 2526 | (let ((buffers (vc-dired-buffers-for-dir (file-name-directory file)))) | ||
| 2527 | (when buffers | ||
| 2528 | (mapcar (lambda (buffer) | ||
| 2529 | (with-current-buffer buffer | ||
| 2530 | (when (dired-goto-file file) | ||
| 2531 | ;; bind vc-dired-terse-mode to nil so that | ||
| 2532 | ;; files won't vanish when they are checked in | ||
| 2533 | (let ((vc-dired-terse-mode nil)) | ||
| 2534 | (dired-do-redisplay 1))))) | ||
| 2535 | buffers)))) | ||
| 2536 | |||
| 2537 | ;;;###autoload | ||
| 2538 | (defun vc-directory (dir read-switches) | ||
| 2539 | "Create a buffer in VC Dired Mode for directory DIR. | ||
| 2540 | |||
| 2541 | See Info node `VC Dired Mode'. | ||
| 2542 | |||
| 2543 | With prefix arg READ-SWITCHES, specify a value to override | ||
| 2544 | `dired-listing-switches' when generating the listing." | ||
| 2545 | (interactive "DDired under VC (directory): \nP") | ||
| 2546 | (let ((vc-dired-switches (concat vc-dired-listing-switches | ||
| 2547 | (if vc-dired-recurse "R" "")))) | ||
| 2548 | (if read-switches | ||
| 2549 | (setq vc-dired-switches | ||
| 2550 | (read-string "Dired listing switches: " | ||
| 2551 | vc-dired-switches))) | ||
| 2552 | (require 'dired) | ||
| 2553 | (require 'dired-aux) | ||
| 2554 | (switch-to-buffer | ||
| 2555 | (dired-internal-noselect (expand-file-name (file-name-as-directory dir)) | ||
| 2556 | vc-dired-switches | ||
| 2557 | 'vc-dired-mode)))) | ||
| 2558 | |||
| 2559 | ;; VC status implementation | ||
| 2560 | |||
| 2561 | ;; Used to store information for the files displayed in the *VC status* buffer. | ||
| 2562 | ;; Each item displayed corresponds to one of these defstructs. | ||
| 2563 | (defstruct (vc-dir-fileinfo | ||
| 2564 | (:copier nil) | ||
| 2565 | (:type list) ;So we can use `member' on lists of FIs. | ||
| 2566 | (:constructor | ||
| 2567 | ;; We could define it as an alias for `list'. | ||
| 2568 | vc-dir-create-fileinfo (name state &optional extra marked directory)) | ||
| 2569 | (:conc-name vc-dir-fileinfo->)) | ||
| 2570 | name ;Keep it as first, for `member'. | ||
| 2571 | state | ||
| 2572 | ;; For storing backend specific information. | ||
| 2573 | extra | ||
| 2574 | marked | ||
| 2575 | ;; To keep track of not updated files during a global refresh | ||
| 2576 | needs-update | ||
| 2577 | ;; To distinguish files and directories. | ||
| 2578 | directory) | ||
| 2579 | |||
| 2580 | (defvar vc-ewoc nil) | ||
| 2581 | |||
| 2582 | (defun vc-default-status-extra-headers (backend dir) | ||
| 2583 | ;; Be loud by default to remind people to add coded to display | ||
| 2584 | ;; backend specific headers. | ||
| 2585 | ;; XXX: change this to return nil before the release. | ||
| 2586 | "Extra : Add backend specific headers here") | ||
| 2587 | |||
| 2588 | (defun vc-dir-headers (backend dir) | ||
| 2589 | "Display the headers in the *VC status* buffer. | ||
| 2590 | It calls the `status-extra-headers' backend method to display backend | ||
| 2591 | specific headers." | ||
| 2592 | (concat | ||
| 2593 | (propertize "VC backend : " 'face 'font-lock-type-face) | ||
| 2594 | (propertize (format "%s\n" backend) 'face 'font-lock-variable-name-face) | ||
| 2595 | (propertize "Working dir: " 'face 'font-lock-type-face) | ||
| 2596 | (propertize (format "%s\n" dir) 'face 'font-lock-variable-name-face) | ||
| 2597 | (vc-call-backend backend 'status-extra-headers dir) | ||
| 2598 | "\n")) | ||
| 2599 | |||
| 2600 | (defun vc-default-status-printer (backend fileentry) | ||
| 2601 | "Pretty print FILEENTRY." | ||
| 2602 | ;; If you change the layout here, change vc-dir-move-to-goal-column. | ||
| 2603 | (let ((state | ||
| 2604 | (if (vc-dir-fileinfo->directory fileentry) | ||
| 2605 | 'DIRECTORY | ||
| 2606 | (vc-dir-fileinfo->state fileentry)))) | ||
| 2607 | (insert | ||
| 2608 | (propertize | ||
| 2609 | (format "%c" (if (vc-dir-fileinfo->marked fileentry) ?* ? )) | ||
| 2610 | 'face 'font-lock-type-face) | ||
| 2611 | " " | ||
| 2612 | (propertize | ||
| 2613 | (format "%-20s" state) | ||
| 2614 | 'face (cond ((eq state 'up-to-date) 'font-lock-builtin-face) | ||
| 2615 | ((memq state '(missing conflict)) 'font-lock-warning-face) | ||
| 2616 | (t 'font-lock-variable-name-face)) | ||
| 2617 | 'mouse-face 'highlight) | ||
| 2618 | " " | ||
| 2619 | (propertize | ||
| 2620 | (format "%s" (vc-dir-fileinfo->name fileentry)) | ||
| 2621 | 'face 'font-lock-function-name-face | ||
| 2622 | 'mouse-face 'highlight)))) | ||
| 2623 | |||
| 2624 | (defun vc-dir-printer (fileentry) | ||
| 2625 | (let ((backend (vc-responsible-backend default-directory))) | ||
| 2626 | (vc-call-backend backend 'status-printer fileentry))) | ||
| 2627 | |||
| 2628 | (defun vc-dir-move-to-goal-column () | ||
| 2629 | ;; Used to keep the cursor on the file name column. | ||
| 2630 | (beginning-of-line) | ||
| 2631 | ;; Must be in sync with vc-default-status-printer. | ||
| 2632 | (forward-char 25)) | ||
| 2633 | |||
| 2634 | (defun vc-dir-prepare-status-buffer (dir &optional create-new) | ||
| 2635 | "Find a *vc-dir* buffer showing DIR, or create a new one." | ||
| 2636 | (setq dir (expand-file-name dir)) | ||
| 2637 | (let* ((bname "*vc-dir*") | ||
| 2638 | ;; Look for another *vc-dir* buffer visiting the same directory. | ||
| 2639 | (buf (save-excursion | ||
| 2640 | (unless create-new | ||
| 2641 | (dolist (buffer (buffer-list)) | ||
| 2642 | (set-buffer buffer) | ||
| 2643 | (when (and (eq major-mode 'vc-dir-mode) | ||
| 2644 | (string= (expand-file-name default-directory) dir)) | ||
| 2645 | (return buffer))))))) | ||
| 2646 | (or buf | ||
| 2647 | ;; Create a new *vc-dir* buffer. | ||
| 2648 | (with-current-buffer (create-file-buffer bname) | ||
| 2649 | (cd dir) | ||
| 2650 | (vc-setup-buffer (current-buffer)) | ||
| 2651 | ;; Reset the vc-parent-buffer-name so that it does not appear | ||
| 2652 | ;; in the mode-line. | ||
| 2653 | (setq vc-parent-buffer-name nil) | ||
| 2654 | (current-buffer))))) | ||
| 2655 | |||
| 2656 | ;;;###autoload | ||
| 2657 | (defun vc-dir (dir) | ||
| 2658 | "Show the VC status for DIR." | ||
| 2659 | (interactive "DVC status for directory: ") | ||
| 2660 | (pop-to-buffer (vc-dir-prepare-status-buffer dir)) | ||
| 2661 | (if (eq major-mode 'vc-dir-mode) | ||
| 2662 | (vc-dir-refresh) | ||
| 2663 | (vc-dir-mode))) | ||
| 2664 | |||
| 2665 | (defvar vc-dir-menu-map | ||
| 2666 | (let ((map (make-sparse-keymap "VC-dir"))) | ||
| 2667 | (define-key map [quit] | ||
| 2668 | '(menu-item "Quit" quit-window | ||
| 2669 | :help "Quit")) | ||
| 2670 | (define-key map [kill] | ||
| 2671 | '(menu-item "Kill Update Command" vc-dir-kill-dir-status-process | ||
| 2672 | :enable (vc-dir-busy) | ||
| 2673 | :help "Kill the command that updates VC status buffer")) | ||
| 2674 | (define-key map [refresh] | ||
| 2675 | '(menu-item "Refresh" vc-dir-refresh | ||
| 2676 | :enable (not (vc-dir-busy)) | ||
| 2677 | :help "Refresh the contents of the VC status buffer")) | ||
| 2678 | (define-key map [remup] | ||
| 2679 | '(menu-item "Hide up-to-date" vc-dir-hide-up-to-date | ||
| 2680 | :help "Hide up-to-date items from display")) | ||
| 2681 | ;; Movement. | ||
| 2682 | (define-key map [sepmv] '("--")) | ||
| 2683 | (define-key map [next-line] | ||
| 2684 | '(menu-item "Next line" vc-dir-next-line | ||
| 2685 | :help "Go to the next line" :keys "n")) | ||
| 2686 | (define-key map [previous-line] | ||
| 2687 | '(menu-item "Previous line" vc-dir-previous-line | ||
| 2688 | :help "Go to the previous line")) | ||
| 2689 | ;; Marking. | ||
| 2690 | (define-key map [sepmrk] '("--")) | ||
| 2691 | (define-key map [unmark-all] | ||
| 2692 | '(menu-item "Unmark All" vc-dir-unmark-all-files | ||
| 2693 | :help "Unmark all files that are in the same state as the current file\ | ||
| 2694 | \nWith prefix argument unmark all files")) | ||
| 2695 | (define-key map [unmark-previous] | ||
| 2696 | '(menu-item "Unmark previous " vc-dir-unmark-file-up | ||
| 2697 | :help "Move to the previous line and unmark the file")) | ||
| 2698 | |||
| 2699 | (define-key map [mark-all] | ||
| 2700 | '(menu-item "Mark All" vc-dir-mark-all-files | ||
| 2701 | :help "Mark all files that are in the same state as the current file\ | ||
| 2702 | \nWith prefix argument mark all files")) | ||
| 2703 | (define-key map [unmark] | ||
| 2704 | '(menu-item "Unmark" vc-dir-unmark | ||
| 2705 | :help "Unmark the current file or all files in the region")) | ||
| 2706 | |||
| 2707 | (define-key map [mark] | ||
| 2708 | '(menu-item "Mark" vc-dir-mark | ||
| 2709 | :help "Mark the current file or all files in the region")) | ||
| 2710 | |||
| 2711 | (define-key map [sepopn] '("--")) | ||
| 2712 | (define-key map [open-other] | ||
| 2713 | '(menu-item "Open in other window" vc-dir-find-file-other-window | ||
| 2714 | :help "Find the file on the current line, in another window")) | ||
| 2715 | (define-key map [open] | ||
| 2716 | '(menu-item "Open file" vc-dir-find-file | ||
| 2717 | :help "Find the file on the current line")) | ||
| 2718 | ;; VC info details | ||
| 2719 | (define-key map [sepvcdet] '("--")) | ||
| 2720 | ;; FIXME: This needs a key binding. And maybe a better name | ||
| 2721 | ;; ("Insert" like PCL-CVS uses does not sound that great either)... | ||
| 2722 | (define-key map [ins] | ||
| 2723 | '(menu-item "Show File" vc-dir-show-fileentry | ||
| 2724 | :help "Show a file in the VC status listing even though it might be up to date")) | ||
| 2725 | (define-key map [annotate] | ||
| 2726 | '(menu-item "Annotate" vc-annotate | ||
| 2727 | :help "Display the edit history of the current file using colors")) | ||
| 2728 | (define-key map [diff] | ||
| 2729 | '(menu-item "Compare with Base Version" vc-diff | ||
| 2730 | :help "Compare file set with the base version")) | ||
| 2731 | (define-key map [log] | ||
| 2732 | '(menu-item "Show history" vc-print-log | ||
| 2733 | :help "List the change log of the current file set in a window")) | ||
| 2734 | ;; VC commands. | ||
| 2735 | (define-key map [sepvccmd] '("--")) | ||
| 2736 | (define-key map [update] | ||
| 2737 | '(menu-item "Update to latest version" vc-update | ||
| 2738 | :help "Update the current fileset's files to their tip revisions")) | ||
| 2739 | (define-key map [revert] | ||
| 2740 | '(menu-item "Revert to base version" vc-revert | ||
| 2741 | :help "Revert working copies of the selected fileset to their repository contents.")) | ||
| 2742 | (define-key map [next-action] | ||
| 2743 | ;; FIXME: This really really really needs a better name! | ||
| 2744 | ;; And a key binding too. | ||
| 2745 | '(menu-item "Check In/Out" vc-next-action | ||
| 2746 | :help "Do the next logical version control operation on the current fileset")) | ||
| 2747 | (define-key map [register] | ||
| 2748 | '(menu-item "Register" vc-dir-register | ||
| 2749 | :help "Register file set into the version control system")) | ||
| 2750 | map) | ||
| 2751 | "Menu for VC status") | ||
| 2752 | |||
| 2753 | (defalias 'vc-dir-menu-map vc-dir-menu-map) | ||
| 2754 | |||
| 2755 | (defvar vc-dir-mode-map | ||
| 2756 | (let ((map (make-keymap))) | ||
| 2757 | (suppress-keymap map) | ||
| 2758 | ;; Marking. | ||
| 2759 | (define-key map "m" 'vc-dir-mark) | ||
| 2760 | (define-key map "M" 'vc-dir-mark-all-files) | ||
| 2761 | (define-key map "u" 'vc-dir-unmark) | ||
| 2762 | (define-key map "U" 'vc-dir-unmark-all-files) | ||
| 2763 | (define-key map "\C-?" 'vc-dir-unmark-file-up) | ||
| 2764 | (define-key map "\M-\C-?" 'vc-dir-unmark-all-files) | ||
| 2765 | ;; Movement. | ||
| 2766 | (define-key map "n" 'vc-dir-next-line) | ||
| 2767 | (define-key map " " 'vc-dir-next-line) | ||
| 2768 | (define-key map "\t" 'vc-dir-next-line) | ||
| 2769 | (define-key map "p" 'vc-dir-previous-line) | ||
| 2770 | (define-key map [backtab] 'vc-dir-previous-line) | ||
| 2771 | ;; VC commands. | ||
| 2772 | (define-key map "=" 'vc-diff) ;; C-x v = | ||
| 2773 | (define-key map "a" 'vc-dir-register) | ||
| 2774 | (define-key map "+" 'vc-update) ;; C-x v + | ||
| 2775 | (define-key map "R" 'vc-revert) ;; u is taken by unmark. | ||
| 2776 | |||
| 2777 | ;; Can't be "g" (as in vc map), so "A" for "Annotate". | ||
| 2778 | (define-key map "A" 'vc-annotate) | ||
| 2779 | (define-key map "l" 'vc-print-log) ;; C-x v l | ||
| 2780 | ;; The remainder. | ||
| 2781 | (define-key map "f" 'vc-dir-find-file) | ||
| 2782 | (define-key map "\C-m" 'vc-dir-find-file) | ||
| 2783 | (define-key map "o" 'vc-dir-find-file-other-window) | ||
| 2784 | (define-key map "x" 'vc-dir-hide-up-to-date) | ||
| 2785 | (define-key map "q" 'quit-window) | ||
| 2786 | (define-key map "g" 'vc-dir-refresh) | ||
| 2787 | (define-key map "\C-c\C-c" 'vc-dir-kill-dir-status-process) | ||
| 2788 | (define-key map [(down-mouse-3)] 'vc-dir-menu) | ||
| 2789 | (define-key map [(mouse-2)] 'vc-dir-toggle-mark) | ||
| 2790 | |||
| 2791 | ;; Hook up the menu. | ||
| 2792 | (define-key map [menu-bar vc-dir-mode] | ||
| 2793 | '(menu-item | ||
| 2794 | ;; This is used to that VC backends could add backend specific | ||
| 2795 | ;; menu items to vc-dir-menu-map. | ||
| 2796 | "VC Status" vc-dir-menu-map :filter vc-dir-menu-map-filter)) | ||
| 2797 | map) | ||
| 2798 | "Keymap for VC status") | ||
| 2799 | |||
| 2800 | (defun vc-default-extra-status-menu (backend) | ||
| 2801 | nil) | ||
| 2802 | |||
| 2803 | ;; This is used to that VC backends could add backend specific menu | ||
| 2804 | ;; items to vc-dir-menu-map. | ||
| 2805 | (defun vc-dir-menu-map-filter (orig-binding) | ||
| 2806 | (when (and (symbolp orig-binding) (fboundp orig-binding)) | ||
| 2807 | (setq orig-binding (indirect-function orig-binding))) | ||
| 2808 | (let ((ext-binding | ||
| 2809 | (vc-call-backend (vc-responsible-backend default-directory) | ||
| 2810 | 'extra-status-menu))) | ||
| 2811 | (if (null ext-binding) | ||
| 2812 | orig-binding | ||
| 2813 | (append orig-binding | ||
| 2814 | '("----") | ||
| 2815 | ext-binding)))) | ||
| 2816 | |||
| 2817 | (defmacro vc-at-event (event &rest body) | ||
| 2818 | "Evaluate `body' wich point located at event-start of `event'. | ||
| 2819 | If `body' uses `event', it should be a variable, | ||
| 2820 | otherwise it will be evaluated twice." | ||
| 2821 | (let ((posn (gensym "vc-at-event-posn"))) | ||
| 2822 | `(let ((,posn (event-start ,event))) | ||
| 2823 | (save-excursion | ||
| 2824 | (set-buffer (window-buffer (posn-window ,posn))) | ||
| 2825 | (goto-char (posn-point ,posn)) | ||
| 2826 | ,@body)))) | ||
| 2827 | |||
| 2828 | (defun vc-dir-menu (e) | ||
| 2829 | "Popup the VC status menu." | ||
| 2830 | (interactive "e") | ||
| 2831 | (vc-at-event e (popup-menu vc-dir-menu-map e))) | ||
| 2832 | |||
| 2833 | (defvar vc-dir-tool-bar-map | ||
| 2834 | (let ((map (make-sparse-keymap))) | ||
| 2835 | (tool-bar-local-item-from-menu 'vc-dir-find-file "open" | ||
| 2836 | map vc-dir-mode-map) | ||
| 2837 | (tool-bar-local-item "bookmark_add" | ||
| 2838 | 'vc-dir-toggle-mark 'vc-dir-toggle-mark map | ||
| 2839 | :help "Toggle mark on current item") | ||
| 2840 | (tool-bar-local-item-from-menu 'vc-dir-previous-line "left-arrow" | ||
| 2841 | map vc-dir-mode-map | ||
| 2842 | :rtl "right-arrow") | ||
| 2843 | (tool-bar-local-item-from-menu 'vc-dir-next-line "right-arrow" | ||
| 2844 | map vc-dir-mode-map | ||
| 2845 | :rtl "left-arrow") | ||
| 2846 | (tool-bar-local-item-from-menu 'vc-print-log "info" | ||
| 2847 | map vc-dir-mode-map) | ||
| 2848 | (tool-bar-local-item-from-menu 'vc-dir-refresh "refresh" | ||
| 2849 | map vc-dir-mode-map) | ||
| 2850 | (tool-bar-local-item-from-menu 'nonincremental-search-forward | ||
| 2851 | "search" map) | ||
| 2852 | (tool-bar-local-item-from-menu 'vc-dir-kill-dir-status-process "cancel" | ||
| 2853 | map vc-dir-mode-map) | ||
| 2854 | (tool-bar-local-item-from-menu 'quit-window "exit" | ||
| 2855 | map vc-dir-mode-map) | ||
| 2856 | map)) | ||
| 2857 | |||
| 2858 | (defvar vc-dir-process-buffer nil | ||
| 2859 | "The buffer used for the asynchronous call that computes the VC status.") | ||
| 2860 | |||
| 2861 | (defun vc-dir-mode () | ||
| 2862 | "Major mode for showing the VC status for a directory. | ||
| 2863 | Marking/Unmarking key bindings and actions: | ||
| 2864 | m - marks a file/directory or ff the region is active, mark all the files | ||
| 2865 | in region. | ||
| 2866 | Restrictions: - a file cannot be marked if any parent directory is marked | ||
| 2867 | - a directory cannot be marked if any child file or | ||
| 2868 | directory is marked | ||
| 2869 | u - marks a file/directory or if the region is active, unmark all the files | ||
| 2870 | in region. | ||
| 2871 | M - if the cursor is on a file: mark all the files with the same VC state as | ||
| 2872 | the current file | ||
| 2873 | - if the cursor is on a directory: mark all child files | ||
| 2874 | - with a prefix argument: mark all files | ||
| 2875 | U - if the cursor is on a file: unmark all the files with the same VC state | ||
| 2876 | as the current file | ||
| 2877 | - if the cursor is on a directory: unmark all child files | ||
| 2878 | - with a prefix argument: unmark all files | ||
| 2879 | |||
| 2880 | |||
| 2881 | \\{vc-dir-mode-map}" | ||
| 2882 | (setq mode-name "VC Status") | ||
| 2883 | (setq major-mode 'vc-dir-mode) | ||
| 2884 | (setq buffer-read-only t) | ||
| 2885 | (use-local-map vc-dir-mode-map) | ||
| 2886 | (set (make-local-variable 'tool-bar-map) vc-dir-tool-bar-map) | ||
| 2887 | (let ((buffer-read-only nil) | ||
| 2888 | (backend (vc-responsible-backend default-directory)) | ||
| 2889 | entries) | ||
| 2890 | (erase-buffer) | ||
| 2891 | (set (make-local-variable 'vc-dir-process-buffer) nil) | ||
| 2892 | (set (make-local-variable 'vc-ewoc) | ||
| 2893 | (ewoc-create #'vc-dir-printer | ||
| 2894 | (vc-dir-headers backend default-directory))) | ||
| 2895 | (add-hook 'after-save-hook 'vc-dir-mark-buffer-changed) | ||
| 2896 | ;; Make sure that if the VC status buffer is killed, the update | ||
| 2897 | ;; process running in the background is also killed. | ||
| 2898 | (add-hook 'kill-buffer-query-functions 'vc-dir-kill-query nil t) | ||
| 2899 | (vc-dir-refresh)) | ||
| 2900 | (run-hooks 'vc-dir-mode-hook)) | ||
| 2901 | |||
| 2902 | (put 'vc-dir-mode 'mode-class 'special) | ||
| 2903 | |||
| 2904 | ;; t if directories should be shown in vc-dir. | ||
| 2905 | ;; WORK IN PROGRESS! DO NOT SET this! ONLY set it if you want to help | ||
| 2906 | ;; write code for this feature. This variable will likely disappear | ||
| 2907 | ;; when the work is done. | ||
| 2908 | (defvar vc-dir-insert-directories nil) | ||
| 2909 | |||
| 2910 | (defun vc-dir-update (entries buffer &optional noinsert) | ||
| 2911 | "Update BUFFER's ewoc from the list of ENTRIES. | ||
| 2912 | If NOINSERT, ignore elements on ENTRIES which are not in the ewoc." | ||
| 2913 | ;; Add ENTRIES to the vc-dir buffer BUFFER. | ||
| 2914 | (with-current-buffer buffer | ||
| 2915 | ;; Insert the entries sorted by name into the ewoc. | ||
| 2916 | ;; We assume the ewoc is sorted too, which should be the | ||
| 2917 | ;; case if we always add entries with vc-dir-update. | ||
| 2918 | (setq entries | ||
| 2919 | ;; Sort: first files and then subdirectories. | ||
| 2920 | ;; XXX: this is VERY inefficient, it computes the directory | ||
| 2921 | ;; names too many times | ||
| 2922 | (sort entries | ||
| 2923 | (lambda (entry1 entry2) | ||
| 2924 | (let ((dir1 (file-name-directory (expand-file-name (car entry1)))) | ||
| 2925 | (dir2 (file-name-directory (expand-file-name (car entry2))))) | ||
| 2926 | (cond | ||
| 2927 | ((string< dir1 dir2) t) | ||
| 2928 | ((not (string= dir1 dir2)) nil) | ||
| 2929 | ((string< (car entry1) (car entry2)))))))) | ||
| 2930 | (if (not vc-dir-insert-directories) | ||
| 2931 | (let ((entry (car entries)) | ||
| 2932 | (node (ewoc-nth vc-ewoc 0))) | ||
| 2933 | (while (and entry node) | ||
| 2934 | (let ((entryfile (car entry)) | ||
| 2935 | (nodefile (vc-dir-fileinfo->name (ewoc-data node)))) | ||
| 2936 | (cond | ||
| 2937 | ((string-lessp nodefile entryfile) | ||
| 2938 | (setq node (ewoc-next vc-ewoc node))) | ||
| 2939 | ((string-lessp entryfile nodefile) | ||
| 2940 | (unless noinsert | ||
| 2941 | (ewoc-enter-before vc-ewoc node | ||
| 2942 | (apply 'vc-dir-create-fileinfo entry))) | ||
| 2943 | (setq entries (cdr entries) entry (car entries))) | ||
| 2944 | (t | ||
| 2945 | (setf (vc-dir-fileinfo->state (ewoc-data node)) (nth 1 entry)) | ||
| 2946 | (setf (vc-dir-fileinfo->extra (ewoc-data node)) (nth 2 entry)) | ||
| 2947 | (setf (vc-dir-fileinfo->needs-update (ewoc-data node)) nil) | ||
| 2948 | (ewoc-invalidate vc-ewoc node) | ||
| 2949 | (setq entries (cdr entries) entry (car entries)) | ||
| 2950 | (setq node (ewoc-next vc-ewoc node)))))) | ||
| 2951 | (unless (or node noinsert) | ||
| 2952 | ;; We're past the last node, all remaining entries go to the end. | ||
| 2953 | (while entries | ||
| 2954 | (ewoc-enter-last vc-ewoc | ||
| 2955 | (apply 'vc-dir-create-fileinfo (pop entries)))))) | ||
| 2956 | ;; Insert directory entries in the right places. | ||
| 2957 | (let ((entry (car entries)) | ||
| 2958 | (node (ewoc-nth vc-ewoc 0))) | ||
| 2959 | ;; Insert . if it is not present. | ||
| 2960 | (unless node | ||
| 2961 | (let ((rd (file-relative-name default-directory))) | ||
| 2962 | (ewoc-enter-last | ||
| 2963 | vc-ewoc (vc-dir-create-fileinfo | ||
| 2964 | rd nil nil nil (expand-file-name default-directory)))) | ||
| 2965 | (setq node (ewoc-nth vc-ewoc 0))) | ||
| 2966 | |||
| 2967 | (while (and entry node) | ||
| 2968 | (let* ((entryfile (car entry)) | ||
| 2969 | (entrydir (file-name-directory (expand-file-name entryfile))) | ||
| 2970 | (nodedir | ||
| 2971 | (or (vc-dir-fileinfo->directory (ewoc-data node)) | ||
| 2972 | (file-name-directory | ||
| 2973 | (expand-file-name | ||
| 2974 | (vc-dir-fileinfo->name (ewoc-data node))))))) | ||
| 2975 | (cond | ||
| 2976 | ;; First try to find the directory. | ||
| 2977 | ((string-lessp nodedir entrydir) | ||
| 2978 | (setq node (ewoc-next vc-ewoc node))) | ||
| 2979 | ((string-equal nodedir entrydir) | ||
| 2980 | ;; Found the directory, find the place for the file name. | ||
| 2981 | (let ((nodefile (vc-dir-fileinfo->name (ewoc-data node)))) | ||
| 2982 | (cond | ||
| 2983 | ((string-lessp nodefile entryfile) | ||
| 2984 | (setq node (ewoc-next vc-ewoc node))) | ||
| 2985 | ((string-equal nodefile entryfile) | ||
| 2986 | (setf (vc-dir-fileinfo->state (ewoc-data node)) (nth 1 entry)) | ||
| 2987 | (setf (vc-dir-fileinfo->extra (ewoc-data node)) (nth 2 entry)) | ||
| 2988 | (setf (vc-dir-fileinfo->needs-update (ewoc-data node)) nil) | ||
| 2989 | (ewoc-invalidate vc-ewoc node) | ||
| 2990 | (setq entries (cdr entries) entry (car entries)) | ||
| 2991 | (setq node (ewoc-next vc-ewoc node))) | ||
| 2992 | (t | ||
| 2993 | (ewoc-enter-before vc-ewoc node | ||
| 2994 | (apply 'vc-dir-create-fileinfo entry)) | ||
| 2995 | (setq entries (cdr entries) entry (car entries)))))) | ||
| 2996 | (t | ||
| 2997 | ;; We need to insert a directory node | ||
| 2998 | (let ((rd (file-relative-name entrydir))) | ||
| 2999 | (ewoc-enter-last | ||
| 3000 | vc-ewoc (vc-dir-create-fileinfo rd nil nil nil entrydir))) | ||
| 3001 | ;; Now insert the node itself. | ||
| 3002 | (ewoc-enter-before vc-ewoc node | ||
| 3003 | (apply 'vc-dir-create-fileinfo entry)) | ||
| 3004 | (setq entries (cdr entries) entry (car entries)))))) | ||
| 3005 | ;; We're past the last node, all remaining entries go to the end. | ||
| 3006 | (unless (or node noinsert) | ||
| 3007 | (let* ((lastnode (ewoc-nth vc-ewoc -1)) | ||
| 3008 | (lastdir | ||
| 3009 | (or (vc-dir-fileinfo->directory (ewoc-data lastnode)) | ||
| 3010 | (file-name-directory | ||
| 3011 | (expand-file-name | ||
| 3012 | (vc-dir-fileinfo->name (ewoc-data lastnode))))))) | ||
| 3013 | (dolist (entry entries) | ||
| 3014 | (let ((entrydir (file-name-directory (expand-file-name (car entry))))) | ||
| 3015 | ;; Insert a directory node if needed. | ||
| 3016 | (unless (string-equal lastdir entrydir) | ||
| 3017 | (setq lastdir entrydir) | ||
| 3018 | (let ((rd (file-relative-name entrydir))) | ||
| 3019 | (ewoc-enter-last | ||
| 3020 | vc-ewoc (vc-dir-create-fileinfo rd nil nil nil entrydir)))) | ||
| 3021 | ;; Now insert the node itself. | ||
| 3022 | (ewoc-enter-last vc-ewoc | ||
| 3023 | (apply 'vc-dir-create-fileinfo entry)))))))))) | ||
| 3024 | |||
| 3025 | (defun vc-dir-busy () | ||
| 3026 | (and (buffer-live-p vc-dir-process-buffer) | ||
| 3027 | (get-buffer-process vc-dir-process-buffer))) | ||
| 3028 | |||
| 3029 | (defun vc-dir-refresh-files (files default-state) | ||
| 3030 | "Refresh some files in the VC status buffer." | ||
| 3031 | (let ((backend (vc-responsible-backend default-directory)) | ||
| 3032 | (status-buffer (current-buffer)) | ||
| 3033 | (def-dir default-directory)) | ||
| 3034 | (vc-set-mode-line-busy-indicator) | ||
| 3035 | ;; Call the `dir-status-file' backend function. | ||
| 3036 | ;; `dir-status-file' is supposed to be asynchronous. | ||
| 3037 | ;; It should compute the results, and then call the function | ||
| 3038 | ;; passed as an argument in order to update the vc-dir buffer | ||
| 3039 | ;; with the results. | ||
| 3040 | (unless (buffer-live-p vc-dir-process-buffer) | ||
| 3041 | (setq vc-dir-process-buffer | ||
| 3042 | (generate-new-buffer (format " *VC-%s* tmp status" backend)))) | ||
| 3043 | (lexical-let ((buffer (current-buffer))) | ||
| 3044 | (with-current-buffer vc-dir-process-buffer | ||
| 3045 | (cd def-dir) | ||
| 3046 | (erase-buffer) | ||
| 3047 | (vc-call-backend | ||
| 3048 | backend 'dir-status-files def-dir files default-state | ||
| 3049 | (lambda (entries &optional more-to-come) | ||
| 3050 | ;; ENTRIES is a list of (FILE VC_STATE EXTRA) items. | ||
| 3051 | ;; If MORE-TO-COME is true, then more updates will come from | ||
| 3052 | ;; the asynchronous process. | ||
| 3053 | (with-current-buffer buffer | ||
| 3054 | (vc-dir-update entries buffer) | ||
| 3055 | (unless more-to-come | ||
| 3056 | (setq mode-line-process nil) | ||
| 3057 | ;; Remove the ones that haven't been updated at all. | ||
| 3058 | ;; Those not-updated are those whose state is nil because the | ||
| 3059 | ;; file/dir doesn't exist and isn't versioned. | ||
| 3060 | (ewoc-filter vc-ewoc | ||
| 3061 | (lambda (info) | ||
| 3062 | (not (vc-dir-fileinfo->needs-update info)))))))))))) | ||
| 3063 | |||
| 3064 | (defun vc-dir-refresh () | ||
| 3065 | "Refresh the contents of the VC status buffer. | ||
| 3066 | Throw an error if another update process is in progress." | ||
| 3067 | (interactive) | ||
| 3068 | (if (vc-dir-busy) | ||
| 3069 | (error "Another update process is in progress, cannot run two at a time") | ||
| 3070 | (let ((backend (vc-responsible-backend default-directory)) | ||
| 3071 | (status-buffer (current-buffer)) | ||
| 3072 | (def-dir default-directory)) | ||
| 3073 | (vc-set-mode-line-busy-indicator) | ||
| 3074 | ;; Call the `dir-status' backend function. | ||
| 3075 | ;; `dir-status' is supposed to be asynchronous. | ||
| 3076 | ;; It should compute the results, and then call the function | ||
| 3077 | ;; passed as an argument in order to update the vc-dir buffer | ||
| 3078 | ;; with the results. | ||
| 3079 | |||
| 3080 | ;; Create a buffer that can be used by `dir-status' and call | ||
| 3081 | ;; `dir-status' with this buffer as the current buffer. Use | ||
| 3082 | ;; `vc-dir-process-buffer' to remember this buffer, so that | ||
| 3083 | ;; it can be used later to kill the update process in case it | ||
| 3084 | ;; takes too long. | ||
| 3085 | (unless (buffer-live-p vc-dir-process-buffer) | ||
| 3086 | (setq vc-dir-process-buffer | ||
| 3087 | (generate-new-buffer (format " *VC-%s* tmp status" backend)))) | ||
| 3088 | ;; set the needs-update flag on all entries | ||
| 3089 | (ewoc-map (lambda (info) (setf (vc-dir-fileinfo->needs-update info) t) nil) | ||
| 3090 | vc-ewoc) | ||
| 3091 | (lexical-let ((buffer (current-buffer))) | ||
| 3092 | (with-current-buffer vc-dir-process-buffer | ||
| 3093 | (cd def-dir) | ||
| 3094 | (erase-buffer) | ||
| 3095 | (vc-call-backend | ||
| 3096 | backend 'dir-status def-dir | ||
| 3097 | (lambda (entries &optional more-to-come) | ||
| 3098 | ;; ENTRIES is a list of (FILE VC_STATE EXTRA) items. | ||
| 3099 | ;; If MORE-TO-COME is true, then more updates will come from | ||
| 3100 | ;; the asynchronous process. | ||
| 3101 | (with-current-buffer buffer | ||
| 3102 | (vc-dir-update entries buffer) | ||
| 3103 | (unless more-to-come | ||
| 3104 | (let ((remaining | ||
| 3105 | (ewoc-collect | ||
| 3106 | vc-ewoc 'vc-dir-fileinfo->needs-update))) | ||
| 3107 | (if remaining | ||
| 3108 | (vc-dir-refresh-files | ||
| 3109 | (mapcar 'vc-dir-fileinfo->name remaining) | ||
| 3110 | 'up-to-date) | ||
| 3111 | (setq mode-line-process nil)))))))))))) | ||
| 3112 | |||
| 3113 | (defun vc-dir-kill-dir-status-process () | ||
| 3114 | "Kill the temporary buffer and associated process." | ||
| 3115 | (interactive) | ||
| 3116 | (when (buffer-live-p vc-dir-process-buffer) | ||
| 3117 | (let ((proc (get-buffer-process vc-dir-process-buffer))) | ||
| 3118 | (when proc (delete-process proc)) | ||
| 3119 | (setq vc-dir-process-buffer nil) | ||
| 3120 | (setq mode-line-process nil)))) | ||
| 3121 | |||
| 3122 | (defun vc-dir-kill-query () | ||
| 3123 | ;; Make sure that when the VC status buffer is killed the update | ||
| 3124 | ;; process running in background is also killed. | ||
| 3125 | (if (vc-dir-busy) | ||
| 3126 | (when (y-or-n-p "Status update process running, really kill status buffer?") | ||
| 3127 | (vc-dir-kill-dir-status-process) | ||
| 3128 | t) | ||
| 3129 | t)) | ||
| 3130 | |||
| 3131 | (defun vc-dir-next-line (arg) | ||
| 3132 | "Go to the next line. | ||
| 3133 | If a prefix argument is given, move by that many lines." | ||
| 3134 | (interactive "p") | ||
| 3135 | (ewoc-goto-next vc-ewoc arg) | ||
| 3136 | (vc-dir-move-to-goal-column)) | ||
| 3137 | |||
| 3138 | (defun vc-dir-previous-line (arg) | ||
| 3139 | "Go to the previous line. | ||
| 3140 | If a prefix argument is given, move by that many lines." | ||
| 3141 | (interactive "p") | ||
| 3142 | (ewoc-goto-prev vc-ewoc arg) | ||
| 3143 | (vc-dir-move-to-goal-column)) | ||
| 3144 | |||
| 3145 | (defun vc-dir-mark-unmark (mark-unmark-function) | ||
| 3146 | (if (use-region-p) | ||
| 3147 | (let ((firstl (line-number-at-pos (region-beginning))) | ||
| 3148 | (lastl (line-number-at-pos (region-end)))) | ||
| 3149 | (save-excursion | ||
| 3150 | (goto-char (region-beginning)) | ||
| 3151 | (while (<= (line-number-at-pos) lastl) | ||
| 3152 | (funcall mark-unmark-function)))) | ||
| 3153 | (funcall mark-unmark-function))) | ||
| 3154 | |||
| 3155 | (defun vc-dir-parent-marked-p (arg) | ||
| 3156 | (when vc-dir-insert-directories | ||
| 3157 | ;; Return nil if none of the parent directories of arg is marked. | ||
| 3158 | (let* ((argdata (ewoc-data arg)) | ||
| 3159 | (argdir | ||
| 3160 | (let ((crtdir (vc-dir-fileinfo->directory argdata))) | ||
| 3161 | (if crtdir | ||
| 3162 | crtdir | ||
| 3163 | (file-name-directory (expand-file-name | ||
| 3164 | (vc-dir-fileinfo->name argdata)))))) | ||
| 3165 | (arglen (length argdir)) | ||
| 3166 | (crt arg) | ||
| 3167 | data dir) | ||
| 3168 | ;; Go through the predecessors, checking if any directory that is | ||
| 3169 | ;; a parent is marked. | ||
| 3170 | (while (setq crt (ewoc-prev vc-ewoc crt)) | ||
| 3171 | (setq data (ewoc-data crt)) | ||
| 3172 | (setq dir | ||
| 3173 | (let ((crtdir (vc-dir-fileinfo->directory data))) | ||
| 3174 | (if crtdir | ||
| 3175 | crtdir | ||
| 3176 | (file-name-directory (expand-file-name | ||
| 3177 | (vc-dir-fileinfo->name data)))))) | ||
| 3178 | |||
| 3179 | (when (and (vc-dir-fileinfo->directory data) | ||
| 3180 | (string-equal (substring argdir 0 (length dir)) dir)) | ||
| 3181 | (when (vc-dir-fileinfo->marked data) | ||
| 3182 | (error "Cannot mark `%s', parent directory `%s' marked" | ||
| 3183 | (vc-dir-fileinfo->name argdata) | ||
| 3184 | (vc-dir-fileinfo->name data))))) | ||
| 3185 | nil))) | ||
| 3186 | |||
| 3187 | (defun vc-dir-children-marked-p (arg) | ||
| 3188 | ;; Return nil if none of the children of arg is marked. | ||
| 3189 | (when vc-dir-insert-directories | ||
| 3190 | (let* ((argdata (ewoc-data arg)) | ||
| 3191 | (argdir (vc-dir-fileinfo->directory argdata)) | ||
| 3192 | (arglen (length argdir)) | ||
| 3193 | (is-child t) | ||
| 3194 | (crt arg) | ||
| 3195 | data dir) | ||
| 3196 | (while (and is-child (setq crt (ewoc-next vc-ewoc crt))) | ||
| 3197 | (setq data (ewoc-data crt)) | ||
| 3198 | (setq dir | ||
| 3199 | (let ((crtdir (vc-dir-fileinfo->directory data))) | ||
| 3200 | (if crtdir | ||
| 3201 | crtdir | ||
| 3202 | (file-name-directory (expand-file-name | ||
| 3203 | (vc-dir-fileinfo->name data)))))) | ||
| 3204 | (if (string-equal argdir (substring dir 0 arglen)) | ||
| 3205 | (when (vc-dir-fileinfo->marked data) | ||
| 3206 | (error "Cannot mark `%s', child `%s' marked" | ||
| 3207 | (vc-dir-fileinfo->name argdata) | ||
| 3208 | (vc-dir-fileinfo->name data))) | ||
| 3209 | ;; We are done, we got to an entry that is not a child of `arg'. | ||
| 3210 | (setq is-child nil))) | ||
| 3211 | nil))) | ||
| 3212 | |||
| 3213 | (defun vc-dir-mark-file (&optional arg) | ||
| 3214 | ;; Mark ARG or the current file and move to the next line. | ||
| 3215 | (let* ((crt (or arg (ewoc-locate vc-ewoc))) | ||
| 3216 | (file (ewoc-data crt)) | ||
| 3217 | (isdir (vc-dir-fileinfo->directory file))) | ||
| 3218 | (when (or (and isdir (not (vc-dir-children-marked-p crt))) | ||
| 3219 | (and (not isdir) (not (vc-dir-parent-marked-p crt)))) | ||
| 3220 | (setf (vc-dir-fileinfo->marked file) t) | ||
| 3221 | (ewoc-invalidate vc-ewoc crt) | ||
| 3222 | (unless (or arg (mouse-event-p last-command-event)) | ||
| 3223 | (vc-dir-next-line 1))))) | ||
| 3224 | |||
| 3225 | (defun vc-dir-mark () | ||
| 3226 | "Mark the current file or all files in the region. | ||
| 3227 | If the region is active, mark all the files in the region. | ||
| 3228 | Otherwise mark the file on the current line and move to the next | ||
| 3229 | line." | ||
| 3230 | (interactive) | ||
| 3231 | (vc-dir-mark-unmark 'vc-dir-mark-file)) | ||
| 3232 | |||
| 3233 | (defun vc-dir-mark-all-files (arg) | ||
| 3234 | "Mark all files with the same state as the current one. | ||
| 3235 | With a prefix argument mark all files. | ||
| 3236 | If the current entry is a directory, mark all child files. | ||
| 3237 | |||
| 3238 | The VC commands operate on files that are on the same state. | ||
| 3239 | This command is intended to make it easy to select all files that | ||
| 3240 | share the same state." | ||
| 3241 | (interactive "P") | ||
| 3242 | (if arg | ||
| 3243 | ;; Mark all files. | ||
| 3244 | (progn | ||
| 3245 | ;; First check that no directory is marked, we can't mark | ||
| 3246 | ;; files in that case. | ||
| 3247 | (ewoc-map | ||
| 3248 | (lambda (filearg) | ||
| 3249 | (when (and (vc-dir-fileinfo->directory filearg) | ||
| 3250 | (vc-dir-fileinfo->directory filearg)) | ||
| 3251 | (error "Cannot mark all files, directory `%s' marked" | ||
| 3252 | (vc-dir-fileinfo->name filearg)))) | ||
| 3253 | vc-ewoc) | ||
| 3254 | (ewoc-map | ||
| 3255 | (lambda (filearg) | ||
| 3256 | (unless (vc-dir-fileinfo->marked filearg) | ||
| 3257 | (setf (vc-dir-fileinfo->marked filearg) t) | ||
| 3258 | t)) | ||
| 3259 | vc-ewoc)) | ||
| 3260 | (let ((data (ewoc-data (ewoc-locate vc-ewoc)))) | ||
| 3261 | (if (vc-dir-fileinfo->directory data) | ||
| 3262 | ;; It's a directory, mark child files. | ||
| 3263 | (let ((crt (ewoc-locate vc-ewoc))) | ||
| 3264 | (unless (vc-dir-children-marked-p crt) | ||
| 3265 | (while (setq crt (ewoc-next vc-ewoc crt)) | ||
| 3266 | (let ((crt-data (ewoc-data crt))) | ||
| 3267 | (unless (vc-dir-fileinfo->directory crt-data) | ||
| 3268 | (setf (vc-dir-fileinfo->marked crt-data) t) | ||
| 3269 | (ewoc-invalidate vc-ewoc crt)))))) | ||
| 3270 | ;; It's a file | ||
| 3271 | (let ((state (vc-dir-fileinfo->state data)) | ||
| 3272 | (crt (ewoc-nth vc-ewoc 0))) | ||
| 3273 | (while crt | ||
| 3274 | (let ((crt-data (ewoc-data crt))) | ||
| 3275 | (when (and (not (vc-dir-fileinfo->marked crt-data)) | ||
| 3276 | (eq (vc-dir-fileinfo->state crt-data) state) | ||
| 3277 | (not (vc-dir-fileinfo->directory crt-data))) | ||
| 3278 | (vc-dir-mark-file crt))) | ||
| 3279 | (setq crt (ewoc-next vc-ewoc crt)))))))) | ||
| 3280 | |||
| 3281 | (defun vc-dir-unmark-file () | ||
| 3282 | ;; Unmark the current file and move to the next line. | ||
| 3283 | (let* ((crt (ewoc-locate vc-ewoc)) | ||
| 3284 | (file (ewoc-data crt))) | ||
| 3285 | (setf (vc-dir-fileinfo->marked file) nil) | ||
| 3286 | (ewoc-invalidate vc-ewoc crt) | ||
| 3287 | (unless (mouse-event-p last-command-event) | ||
| 3288 | (vc-dir-next-line 1)))) | ||
| 3289 | |||
| 3290 | (defun vc-dir-unmark () | ||
| 3291 | "Unmark the current file or all files in the region. | ||
| 3292 | If the region is active, unmark all the files in the region. | ||
| 3293 | Otherwise mark the file on the current line and move to the next | ||
| 3294 | line." | ||
| 3295 | (interactive) | ||
| 3296 | (vc-dir-mark-unmark 'vc-dir-unmark-file)) | ||
| 3297 | |||
| 3298 | (defun vc-dir-unmark-file-up () | ||
| 3299 | "Move to the previous line and unmark the file." | ||
| 3300 | (interactive) | ||
| 3301 | ;; If we're on the first line, we won't move up, but we will still | ||
| 3302 | ;; remove the mark. This seems a bit odd but it is what buffer-menu | ||
| 3303 | ;; does. | ||
| 3304 | (let* ((prev (ewoc-goto-prev vc-ewoc 1)) | ||
| 3305 | (file (ewoc-data prev))) | ||
| 3306 | (setf (vc-dir-fileinfo->marked file) nil) | ||
| 3307 | (ewoc-invalidate vc-ewoc prev) | ||
| 3308 | (vc-dir-move-to-goal-column))) | ||
| 3309 | |||
| 3310 | (defun vc-dir-unmark-all-files (arg) | ||
| 3311 | "Unmark all files with the same state as the current one. | ||
| 3312 | With a prefix argument unmark all files. | ||
| 3313 | If the current entry is a directory, unmark all the child files. | ||
| 3314 | |||
| 3315 | The VC commands operate on files that are on the same state. | ||
| 3316 | This command is intended to make it easy to deselect all files | ||
| 3317 | that share the same state." | ||
| 3318 | (interactive "P") | ||
| 3319 | (if arg | ||
| 3320 | (ewoc-map | ||
| 3321 | (lambda (filearg) | ||
| 3322 | (when (vc-dir-fileinfo->marked filearg) | ||
| 3323 | (setf (vc-dir-fileinfo->marked filearg) nil) | ||
| 3324 | t)) | ||
| 3325 | vc-ewoc) | ||
| 3326 | (let* ((crt (ewoc-locate vc-ewoc)) | ||
| 3327 | (data (ewoc-data crt))) | ||
| 3328 | (if (vc-dir-fileinfo->directory data) | ||
| 3329 | ;; It's a directory, unmark child files. | ||
| 3330 | (while (setq crt (ewoc-next vc-ewoc crt)) | ||
| 3331 | (let ((crt-data (ewoc-data crt))) | ||
| 3332 | (unless (vc-dir-fileinfo->directory crt-data) | ||
| 3333 | (setf (vc-dir-fileinfo->marked crt-data) nil) | ||
| 3334 | (ewoc-invalidate vc-ewoc crt)))) | ||
| 3335 | ;; It's a file | ||
| 3336 | (let ((crt-state (vc-dir-fileinfo->state (ewoc-data crt)))) | ||
| 3337 | (ewoc-map | ||
| 3338 | (lambda (filearg) | ||
| 3339 | (when (and (vc-dir-fileinfo->marked filearg) | ||
| 3340 | (eq (vc-dir-fileinfo->state filearg) crt-state)) | ||
| 3341 | (setf (vc-dir-fileinfo->marked filearg) nil) | ||
| 3342 | t)) | ||
| 3343 | vc-ewoc)))))) | ||
| 3344 | |||
| 3345 | (defun vc-dir-toggle-mark-file () | ||
| 3346 | (let* ((crt (ewoc-locate vc-ewoc)) | ||
| 3347 | (file (ewoc-data crt))) | ||
| 3348 | (if (vc-dir-fileinfo->marked file) | ||
| 3349 | (vc-dir-unmark-file) | ||
| 3350 | (vc-dir-mark-file)))) | ||
| 3351 | |||
| 3352 | (defun vc-dir-toggle-mark (e) | ||
| 3353 | (interactive "e") | ||
| 3354 | (vc-at-event e (vc-dir-mark-unmark 'vc-dir-toggle-mark-file))) | ||
| 3355 | |||
| 3356 | (defun vc-dir-register () | ||
| 3357 | "Register the marked files, or the current file if no marks." | ||
| 3358 | (interactive) | ||
| 3359 | ;; FIXME: Just pass the fileset to vc-register. | ||
| 3360 | (mapc (lambda (arg) (vc-register nil arg)) | ||
| 3361 | (or (vc-dir-marked-files) (list (vc-dir-current-file))))) | ||
| 3362 | |||
| 3363 | (defun vc-dir-delete-file () | ||
| 3364 | "Delete the marked files, or the current file if no marks." | ||
| 3365 | (interactive) | ||
| 3366 | (mapc 'vc-delete-file (or (vc-dir-marked-files) | ||
| 3367 | (list (vc-dir-current-file))))) | ||
| 3368 | |||
| 3369 | (defun vc-dir-show-fileentry (file) | ||
| 3370 | "Insert an entry for a specific file into the current VC status listing. | ||
| 3371 | This is typically used if the file is up-to-date (or has been added | ||
| 3372 | outside of VC) and one wants to do some operation on it." | ||
| 3373 | (interactive "fShow file: ") | ||
| 3374 | (vc-dir-update (list (list (file-relative-name file) (vc-state file))) (current-buffer))) | ||
| 3375 | |||
| 3376 | (defun vc-dir-find-file () | ||
| 3377 | "Find the file on the current line." | ||
| 3378 | (interactive) | ||
| 3379 | (find-file (vc-dir-current-file))) | ||
| 3380 | |||
| 3381 | (defun vc-dir-find-file-other-window () | ||
| 3382 | "Find the file on the current line, in another window." | ||
| 3383 | (interactive) | ||
| 3384 | (find-file-other-window (vc-dir-current-file))) | ||
| 3385 | |||
| 3386 | (defun vc-dir-current-file () | ||
| 3387 | (let ((node (ewoc-locate vc-ewoc))) | ||
| 3388 | (unless node | ||
| 3389 | (error "No file available.")) | ||
| 3390 | (expand-file-name (vc-dir-fileinfo->name (ewoc-data node))))) | ||
| 3391 | |||
| 3392 | (defun vc-dir-marked-files () | ||
| 3393 | "Return the list of marked files." | ||
| 3394 | (mapcar | ||
| 3395 | (lambda (elem) (expand-file-name (vc-dir-fileinfo->name elem))) | ||
| 3396 | (ewoc-collect vc-ewoc 'vc-dir-fileinfo->marked))) | ||
| 3397 | |||
| 3398 | (defun vc-dir-marked-only-files () | ||
| 3399 | "Return the list of marked files, for marked directories, return child files." | ||
| 3400 | |||
| 3401 | (let ((crt (ewoc-nth vc-ewoc 0)) | ||
| 3402 | result) | ||
| 3403 | (while crt | ||
| 3404 | (let ((crt-data (ewoc-data crt))) | ||
| 3405 | (if (vc-dir-fileinfo->marked crt-data) | ||
| 3406 | (if (vc-dir-fileinfo->directory crt-data) | ||
| 3407 | (let* ((dir (vc-dir-fileinfo->directory crt-data)) | ||
| 3408 | (dirlen (length dir)) | ||
| 3409 | data) | ||
| 3410 | (while | ||
| 3411 | (and (setq crt (ewoc-next vc-ewoc crt)) | ||
| 3412 | (string-equal | ||
| 3413 | (substring | ||
| 3414 | (progn | ||
| 3415 | (setq data (ewoc-data crt)) | ||
| 3416 | (let ((crtdir (vc-dir-fileinfo->directory data))) | ||
| 3417 | (if crtdir | ||
| 3418 | crtdir | ||
| 3419 | (file-name-directory | ||
| 3420 | (expand-file-name | ||
| 3421 | (vc-dir-fileinfo->name data)))))) | ||
| 3422 | 0 dirlen) | ||
| 3423 | dir)) | ||
| 3424 | (unless (vc-dir-fileinfo->directory data) | ||
| 3425 | (push (vc-dir-fileinfo->name data) result)))) | ||
| 3426 | (push (expand-file-name (vc-dir-fileinfo->name crt-data)) result) | ||
| 3427 | (setq crt (ewoc-next vc-ewoc crt))) | ||
| 3428 | (setq crt (ewoc-next vc-ewoc crt))))) | ||
| 3429 | result)) | ||
| 3430 | |||
| 3431 | (defun vc-dir-hide-up-to-date () | ||
| 3432 | "Hide up-to-date items from display." | ||
| 3433 | (interactive) | ||
| 3434 | (ewoc-filter | ||
| 3435 | vc-ewoc | ||
| 3436 | (lambda (crt) (not (eq (vc-dir-fileinfo->state crt) 'up-to-date))))) | ||
| 3437 | |||
| 3438 | (defun vc-default-status-fileinfo-extra (backend file) | ||
| 3439 | nil) | ||
| 3440 | |||
| 3441 | (defun vc-dir-mark-buffer-changed (&optional fname) | ||
| 3442 | (let* ((file (or fname (expand-file-name buffer-file-name))) | ||
| 3443 | (found-vc-dir-buf nil)) | ||
| 3444 | (save-excursion | ||
| 3445 | (dolist (status-buf (buffer-list)) | ||
| 3446 | (set-buffer status-buf) | ||
| 3447 | ;; look for a vc-dir buffer that might show this file. | ||
| 3448 | (when (eq major-mode 'vc-dir-mode) | ||
| 3449 | (setq found-vc-dir-buf t) | ||
| 3450 | (let ((ddir (expand-file-name default-directory))) | ||
| 3451 | ;; This test is cvs-string-prefix-p | ||
| 3452 | (when (eq t (compare-strings file nil (length ddir) ddir nil nil)) | ||
| 3453 | (let* | ||
| 3454 | ((file-short (substring file (length ddir))) | ||
| 3455 | (backend (vc-backend file)) | ||
| 3456 | (state (and backend (vc-state file))) | ||
| 3457 | (extra | ||
| 3458 | (and backend | ||
| 3459 | (vc-call-backend backend 'status-fileinfo-extra file))) | ||
| 3460 | (entry | ||
| 3461 | (list file-short (if state state 'unregistered) extra))) | ||
| 3462 | (vc-dir-update (list entry) status-buf)))))) | ||
| 3463 | ;; We didn't find any vc-dir buffers, remove the hook, it is | ||
| 3464 | ;; not needed. | ||
| 3465 | (unless found-vc-dir-buf (remove-hook 'after-save-hook 'vc-dir-mark-buffer-changed))))) | ||
| 3466 | |||
| 2101 | ;; Named-configuration entry points | 3467 | ;; Named-configuration entry points |
| 2102 | 3468 | ||
| 2103 | (defun vc-snapshot-precondition (dir) | 3469 | (defun vc-snapshot-precondition (dir) |
| @@ -3307,6 +4673,30 @@ The annotations are relative to the current time, unless overridden by OFFSET." | |||
| 3307 | (set-buffer-modified-p nil) | 4673 | (set-buffer-modified-p nil) |
| 3308 | (setq buffer-file-name nil)) | 4674 | (setq buffer-file-name nil)) |
| 3309 | 4675 | ||
| 4676 | ;; These things should probably be generally available | ||
| 4677 | |||
| 4678 | (defun vc-file-tree-walk (dirname func &rest args) | ||
| 4679 | "Walk recursively through DIRNAME. | ||
| 4680 | Invoke FUNC f ARGS on each VC-managed file f underneath it." | ||
| 4681 | (vc-file-tree-walk-internal (expand-file-name dirname) func args) | ||
| 4682 | (message "Traversing directory %s...done" dirname)) | ||
| 4683 | |||
| 4684 | (defun vc-file-tree-walk-internal (file func args) | ||
| 4685 | (if (not (file-directory-p file)) | ||
| 4686 | (when (vc-backend file) (apply func file args)) | ||
| 4687 | (message "Traversing directory %s..." (abbreviate-file-name file)) | ||
| 4688 | (let ((dir (file-name-as-directory file))) | ||
| 4689 | (mapcar | ||
| 4690 | (lambda (f) (or | ||
| 4691 | (string-equal f ".") | ||
| 4692 | (string-equal f "..") | ||
| 4693 | (member f vc-directory-exclusion-list) | ||
| 4694 | (let ((dirf (expand-file-name f dir))) | ||
| 4695 | (or | ||
| 4696 | (file-symlink-p dirf) ;; Avoid possible loops. | ||
| 4697 | (vc-file-tree-walk-internal dirf func args))))) | ||
| 4698 | (directory-files dir))))) | ||
| 4699 | |||
| 3310 | (provide 'vc) | 4700 | (provide 'vc) |
| 3311 | 4701 | ||
| 3312 | ;; DEVELOPER'S NOTES ON CONCURRENCY PROBLEMS IN THIS CODE | 4702 | ;; DEVELOPER'S NOTES ON CONCURRENCY PROBLEMS IN THIS CODE |