|
21 | 21 | (require 'majutsu-config) |
22 | 22 | (require 'majutsu-selection) |
23 | 23 | (require 'majutsu-section) |
| 24 | +(require 'majutsu-file) |
24 | 25 | (require 'magit-diff) ; for faces/font-lock keywords |
25 | 26 | (require 'diff-mode) |
26 | 27 | (require 'smerge-mode) |
@@ -747,31 +748,146 @@ works with the simplified jj diff we render here." |
747 | 748 | (when-let* ((file (majutsu-diff--file-at-point))) |
748 | 749 | (find-file (expand-file-name file default-directory)))) |
749 | 750 |
|
| 751 | +(defun majutsu-diff--range-value (range prefix) |
| 752 | + "Return the value in RANGE for argument starting with PREFIX." |
| 753 | + (when range |
| 754 | + (when-let* ((arg (seq-find (lambda (item) (string-prefix-p prefix item)) range))) |
| 755 | + (substring arg (length prefix))))) |
| 756 | + |
| 757 | +(defun majutsu-diff--on-removed-line-p () |
| 758 | + "Return non-nil if point is on a removed diff line." |
| 759 | + (eq (char-after (line-beginning-position)) ?-)) |
| 760 | + |
| 761 | +(defun majutsu-diff--default-revset () |
| 762 | + "Return the revset implied by the current diff buffer." |
| 763 | + (let* ((range majutsu-buffer-diff-range) |
| 764 | + (removed (majutsu-diff--on-removed-line-p)) |
| 765 | + (from (majutsu-diff--range-value range "--from=")) |
| 766 | + (to (majutsu-diff--range-value range "--to=")) |
| 767 | + (revisions (majutsu-diff--range-value range "--revisions="))) |
| 768 | + (cond |
| 769 | + ((and range (equal (car range) "-r") (cadr range)) (cadr range)) |
| 770 | + (revisions revisions) |
| 771 | + (from (if (and removed from) from (or to from))) |
| 772 | + (t "@")))) |
| 773 | + |
| 774 | +(defun majutsu-diff--hunk-line (section goto-from) |
| 775 | + "Return the line number in SECTION for GOTO-FROM side." |
| 776 | + (with-slots (content from-range to-range) section |
| 777 | + (let ((start (car (if goto-from from-range to-range)))) |
| 778 | + (when start |
| 779 | + (let ((line start) |
| 780 | + (target (point))) |
| 781 | + (save-excursion |
| 782 | + (goto-char content) |
| 783 | + (while (< (point) target) |
| 784 | + (let ((ch (char-after (line-beginning-position)))) |
| 785 | + (cond |
| 786 | + ((eq ch ?+) (unless goto-from (setq line (1+ line)))) |
| 787 | + ((eq ch ?-) (when goto-from (setq line (1+ line)))) |
| 788 | + (t (setq line (1+ line))))) |
| 789 | + (forward-line 1))) |
| 790 | + line))))) |
| 791 | + |
| 792 | +(defun majutsu-diff--hunk-column (section goto-from) |
| 793 | + "Return the column for SECTION based on GOTO-FROM side." |
| 794 | + (let ((bol (line-beginning-position))) |
| 795 | + (if (or (< (point) (oref section content)) |
| 796 | + (and (not goto-from) (eq (char-after bol) ?-))) |
| 797 | + 0 |
| 798 | + (let ((col (current-column))) |
| 799 | + (if (memq (char-after bol) '(?+ ?-)) |
| 800 | + (max 0 (1- col)) |
| 801 | + col))))) |
| 802 | + |
| 803 | +(defun majutsu-diff--goto-line-col (buffer line col) |
| 804 | + "Move point in BUFFER to LINE and COL." |
| 805 | + (with-current-buffer buffer |
| 806 | + (widen) |
| 807 | + (goto-char (point-min)) |
| 808 | + (forward-line (max 0 (1- line))) |
| 809 | + (move-to-column col))) |
| 810 | + |
| 811 | +(defun majutsu-diff--visit-workspace-p () |
| 812 | + "Return non-nil if the current diff should visit the workspace file. |
| 813 | +This is true when diffing the working copy (@) on the new/right side." |
| 814 | + (let* ((range majutsu-buffer-diff-range) |
| 815 | + (to (majutsu-diff--range-value range "--to=")) |
| 816 | + (revisions (majutsu-diff--range-value range "--revisions="))) |
| 817 | + (cond |
| 818 | + ;; Explicit --to=@ means we're looking at working copy changes |
| 819 | + ((equal to "@") t) |
| 820 | + ;; No range specified defaults to -r @ (working copy) |
| 821 | + ((null range) t) |
| 822 | + ;; Single revision diff (-r @) shows working copy |
| 823 | + ((and revisions (equal revisions "@")) t) |
| 824 | + ;; Otherwise we're looking at committed changes |
| 825 | + (t nil)))) |
| 826 | + |
750 | 827 | ;;;###autoload |
751 | | -(defun majutsu-diff-visit-file () |
752 | | - "Visit the file at point. |
| 828 | +(defun majutsu-diff-visit-file (&optional force-workspace) |
| 829 | + "From a diff, visit the appropriate version of the file at point. |
753 | 830 |
|
754 | | -When point is on a hunk section, jump to the corresponding line in the |
755 | | -file." |
756 | | - (interactive) |
757 | | - (let ((section (magit-current-section))) |
758 | | - (cond |
759 | | - ((and section (magit-section-match 'jj-hunk section)) |
760 | | - (majutsu-goto-diff-line)) |
761 | | - ((majutsu-diff--file-at-point) |
762 | | - (majutsu-visit-file)) |
763 | | - (t |
764 | | - (user-error "No file at point"))))) |
| 831 | +If point is on an added or context line, visit the new/right side. |
| 832 | +If point is on a removed line, visit the old/left side. |
| 833 | +
|
| 834 | +For diffs of the working copy (@), this visits the actual file in |
| 835 | +the workspace. For diffs of committed changes, this visits the |
| 836 | +blob from the appropriate revision. |
| 837 | +
|
| 838 | +With prefix argument FORCE-WORKSPACE, always visit the workspace file |
| 839 | +regardless of what the diff is about." |
| 840 | + (interactive "P") |
| 841 | + (let* ((section (magit-current-section)) |
| 842 | + (file (majutsu-diff--file-at-point))) |
| 843 | + (unless file |
| 844 | + (user-error "No file at point")) |
| 845 | + (let* ((goto-from (and section (magit-section-match 'jj-hunk section) |
| 846 | + (majutsu-diff--on-removed-line-p))) |
| 847 | + (goto-workspace (or force-workspace |
| 848 | + (and (majutsu-diff--visit-workspace-p) |
| 849 | + (not goto-from)))) |
| 850 | + (line (and section (magit-section-match 'jj-hunk section) |
| 851 | + (majutsu-diff--hunk-line section goto-from))) |
| 852 | + (col (and section (magit-section-match 'jj-hunk section) |
| 853 | + (majutsu-diff--hunk-column section goto-from)))) |
| 854 | + (if goto-workspace |
| 855 | + ;; Visit workspace file |
| 856 | + (let ((full-path (expand-file-name file default-directory))) |
| 857 | + (if (file-exists-p full-path) |
| 858 | + (progn |
| 859 | + (find-file full-path) |
| 860 | + (when (and line col) |
| 861 | + (goto-char (point-min)) |
| 862 | + (forward-line (1- line)) |
| 863 | + (move-to-column col))) |
| 864 | + (user-error "File does not exist in workspace: %s" file))) |
| 865 | + ;; Visit blob |
| 866 | + (let* ((revset (majutsu-diff--default-revset)) |
| 867 | + (buf (majutsu-find-file revset file))) |
| 868 | + (when (and buf line col) |
| 869 | + (majutsu-diff--goto-line-col buf line col))))))) |
765 | 870 |
|
766 | 871 | ;;; Section Keymaps |
767 | 872 |
|
768 | 873 | (defvar-keymap majutsu-diff-section-map |
769 | 874 | :doc "Keymap for diff sections." |
770 | | - "<remap> <majutsu-visit-thing>" #'majutsu-diff-visit-file) |
| 875 | + "<remap> <majutsu-visit-thing>" #'majutsu-diff-visit-file |
| 876 | + "C-j" #'majutsu-diff-visit-workspace-file |
| 877 | + "C-<return>" #'majutsu-diff-visit-workspace-file) |
| 878 | + |
| 879 | +;;;###autoload |
| 880 | +(defun majutsu-diff-visit-workspace-file () |
| 881 | + "From a diff, visit the workspace version of the file at point. |
| 882 | +Always visits the actual file in the working tree, regardless of |
| 883 | +what the diff is about." |
| 884 | + (interactive) |
| 885 | + (majutsu-diff-visit-file t)) |
771 | 886 |
|
772 | 887 | (defvar-keymap majutsu-file-section-map |
773 | 888 | :doc "Keymap for `jj-file' sections." |
774 | | - :parent majutsu-diff-section-map) |
| 889 | + :parent majutsu-diff-section-map |
| 890 | + "v" #'majutsu-find-file-at-point) |
775 | 891 |
|
776 | 892 | (defvar-keymap majutsu-hunk-section-map |
777 | 893 | :doc "Keymap for `jj-hunk' sections." |
|
0 commit comments