;;; magit-bookmark.el --- bookmark support for Magit -*- lexical-binding: t -*- ;; Copyright (C) 2010-2018 The Magit Project Contributors ;; ;; You should have received a copy of the AUTHORS.md file which ;; lists all contributors. If not, see http://magit.vc/authors. ;; Author: Yuri Khan ;; Maintainer: Jonas Bernoulli ;; Magit is free software; you can redistribute it and/or modify it ;; under the terms of the GNU General Public License as published by ;; the Free Software Foundation; either version 3, or (at your option) ;; any later version. ;; ;; Magit is distributed in the hope that it will be useful, but WITHOUT ;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY ;; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public ;; License for more details. ;; ;; You should have received a copy of the GNU General Public License ;; along with Magit. If not, see http://www.gnu.org/licenses. ;;; Commentary: ;; Support for bookmarks for most Magit buffers. ;;; Code: (require 'magit) (require 'bookmark) ;;; Supporting primitives (defun magit-bookmark--jump (bookmark fn &rest args) "Handle a Magit BOOKMARK. This function will: 1. Bind `default-directory' to the repository root directory stored in the `filename' bookmark property. 2. Invoke the function FN with ARGS as arguments. This needs to restore the buffer. 3. Restore the expanded/collapsed status of top level sections and the point position." (declare (indent 2)) (let* ((default-directory (bookmark-get-filename bookmark))) (if default-directory (apply fn args) (signal 'bookmark-error-no-filename (list 'stringp default-directory))) (when (derived-mode-p 'magit-mode) (when-let ((hidden-sections (bookmark-prop-get bookmark 'magit-hidden-sections))) (dolist (child (oref magit-root-section children)) (if (member (cons (oref child type) (oref child value)) hidden-sections) (magit-section-hide child) (magit-section-show child))))) (--when-let (bookmark-get-position bookmark) (goto-char it)) (--when-let (bookmark-get-front-context-string bookmark) (when (search-forward it (point-max) t) (goto-char (match-beginning 0)))) (--when-let (bookmark-get-rear-context-string bookmark) (when (search-backward it (point-min) t) (goto-char (match-end 0)))) nil)) (defun magit-bookmark--make-record (mode handler &optional make-props) "Create a Magit bookmark. MODE specifies the expected major mode of current buffer. HANDLER should be a function that will be used to restore this buffer. MAKE-PROPS should be either nil or a function that will be called with `magit-refresh-args' as the argument list, and may return an alist whose every element has the form (PROP . VALUE) and specifies additional properties to store in the bookmark." (declare (indent 1)) (unless (eq major-mode mode) (user-error "Not in a %s buffer" mode)) (let ((bookmark (bookmark-make-record-default 'no-file))) (bookmark-prop-set bookmark 'handler handler) (bookmark-set-filename bookmark (magit-toplevel)) (when (derived-mode-p 'magit-mode) (bookmark-prop-set bookmark 'magit-hidden-sections (--map (cons (oref it type) (oref it value)) (--filter (oref it hidden) (oref magit-root-section children))))) (when make-props (pcase-dolist (`(,prop . ,value) (apply make-props magit-refresh-args)) (bookmark-prop-set bookmark prop value))) bookmark)) ;;; Status ;;;###autoload (defun magit-bookmark--status-jump (bookmark) "Handle a Magit status BOOKMARK." (magit-bookmark--jump bookmark (lambda () (magit-status-internal default-directory)))) ;;;###autoload (defun magit-bookmark--status-make-record () "Create a Magit status bookmark." (magit-bookmark--make-record 'magit-status-mode #'magit-bookmark--status-jump)) ;;; Refs ;;;###autoload (defun magit-bookmark--refs-jump (bookmark) "Handle a Magit refs BOOKMARK." (magit-bookmark--jump bookmark #'magit-show-refs (bookmark-prop-get bookmark 'magit-refs) (bookmark-prop-get bookmark 'magit-args))) ;;;###autoload (defun magit-bookmark--refs-make-record () "Create a Magit refs bookmark." (magit-bookmark--make-record 'magit-refs-mode #'magit-bookmark--refs-jump (lambda (refs args) `((magit-refs . ,refs) (magit-args . ,args))))) ;;; Log ;;;###autoload (defun magit-bookmark--log-jump (bookmark) "Handle a Magit log BOOKMARK." (magit-bookmark--jump bookmark #'magit-log (bookmark-prop-get bookmark 'magit-revs) (bookmark-prop-get bookmark 'magit-args) (bookmark-prop-get bookmark 'magit-files))) (defun magit-bookmark--log-make-name (buffer-name revs _args files) "Generate the default name for a log bookmark." (concat buffer-name " " (mapconcat #'identity revs " ") (and files (concat " touching " (mapconcat #'identity files " "))))) ;;;###autoload (defun magit-bookmark--log-make-record () "Create a Magit log bookmark." (magit-bookmark--make-record 'magit-log-mode #'magit-bookmark--log-jump (lambda (revs args files) `((defaults . (,(magit-bookmark--log-make-name (buffer-name) revs args files))) (magit-revs . ,revs) (magit-args . ,args) (magit-files . ,files))))) ;;; Reflog ;;;###autoload (defun magit-bookmark--reflog-jump (bookmark) "Handle a Magit reflog BOOKMARK." (magit-bookmark--jump bookmark (lambda () (let ((magit-reflog-arguments (bookmark-prop-get bookmark 'magit-args))) (magit-reflog (bookmark-prop-get bookmark 'magit-ref)))))) (defun magit-bookmark--reflog-make-name (buffer-name ref) "Generate the default name for a reflog bookmark." (concat buffer-name " " ref)) ;;;###autoload (defun magit-bookmark--reflog-make-record () "Create a Magit reflog bookmark." (magit-bookmark--make-record 'magit-reflog-mode #'magit-bookmark--reflog-jump (lambda (ref args) `((defaults . (,(magit-bookmark--reflog-make-name (buffer-name) ref))) (magit-ref . ,ref) (magit-args . ,args))))) ;;; Stashes ;;;###autoload (defun magit-bookmark--stashes-jump (bookmark) "Handle a Magit stash list BOOKMARK." (magit-bookmark--jump bookmark #'magit-stash-list)) ;;;###autoload (defun magit-bookmark--stashes-make-record () "Create a Magit stash list bookmark." (magit-bookmark--make-record 'magit-stashes-mode #'magit-bookmark--stashes-jump)) ;;; Cherry ;;;###autoload (defun magit-bookmark--cherry-jump (bookmark) "Handle a Magit cherry BOOKMARK." (magit-bookmark--jump bookmark #'magit-cherry (bookmark-prop-get bookmark 'magit-head) (bookmark-prop-get bookmark 'magit-upstream))) (defun magit-bookmark--cherry-make-name (buffer-name head upstream) "Generate the default name for a cherry bookmark." (concat buffer-name " " head " upstream " upstream)) ;;;###autoload (defun magit-bookmark--cherry-make-record () "Create a Magit cherry bookmark." (magit-bookmark--make-record 'magit-cherry-mode #'magit-bookmark--cherry-jump (lambda (upstream head) `((defaults . (,(magit-bookmark--cherry-make-name (buffer-name) head upstream))) (magit-head . ,head) (magit-upstream . ,upstream))))) ;;; Diff ;;;###autoload (defun magit-bookmark--diff-jump (bookmark) "Handle a Magit diff BOOKMARK." (magit-bookmark--jump bookmark #'magit-diff-setup (bookmark-prop-get bookmark 'magit-rev-or-range) (bookmark-prop-get bookmark 'magit-const) (bookmark-prop-get bookmark 'magit-args) (bookmark-prop-get bookmark 'magit-files))) (defun magit-bookmark--resolve (rev-or-range) "Return REV-OR-RANGE with ref names resolved to commit hashes." (pcase (magit-git-lines "rev-parse" rev-or-range) (`(,rev) (magit-rev-abbrev rev)) ((and `(,rev1 ,rev2) (guard (/= ?^ (aref rev1 0))) (guard (= ?^ (aref rev2 0)))) (concat (magit-rev-abbrev (substring rev2 1)) ".." (magit-rev-abbrev rev1))) ((and `(,rev1 ,rev2 ,rev3) (guard (/= ?^ (aref rev1 0))) (guard (/= ?^ (aref rev2 0))) (guard (= ?^ (aref rev3 0)))) (ignore rev3) (concat (magit-rev-abbrev rev1) "..." (magit-rev-abbrev rev2))) (_ rev-or-range))) (defun magit-bookmark--diff-make-name (buffer-name rev-or-range const _args files) "Generate a default name for a diff bookmark." (if (member "--no-index" const) (apply #'format "*magit-diff %s %s" files) (concat buffer-name " " (cond (rev-or-range) ((member "--cached" const) "staged") (t "unstaged")) (when files (concat " in " (mapconcat #'identity files ", ")))))) ;;;###autoload (defun magit-bookmark--diff-make-record () "Create a Magit diff bookmark." (magit-bookmark--make-record 'magit-diff-mode #'magit-bookmark--diff-jump (lambda (rev-or-range const args files) (let ((resolved (magit-bookmark--resolve rev-or-range))) `((defaults . (,(magit-bookmark--diff-make-name (buffer-name) resolved const args files))) (magit-rev-or-range . ,resolved) (magit-const . ,const) (magit-args . ,args) (magit-files . ,files)))))) ;;; Revision ;;;###autoload (defun magit-bookmark--revision-jump (bookmark) "Handle a Magit revision BOOKMARK." (magit-bookmark--jump bookmark #'magit-show-commit (bookmark-prop-get bookmark 'magit-rev) (bookmark-prop-get bookmark 'args) (bookmark-prop-get bookmark 'files))) (defun magit-bookmark--revision-make-name (buffer-name rev _args files) "Generate a default name for a revision bookmark." (let ((subject (magit-rev-format "%s" rev))) (concat buffer-name " " (magit-rev-abbrev rev) (cond (files (concat " " (mapconcat #'identity files " "))) (subject (concat " " subject)))))) ;;;###autoload (defun magit-bookmark--revision-make-record () "Create a Magit revision bookmark." ;; magit-refresh-args stores the revision in relative form. ;; For bookmarks, the exact hash is more appropriate. (magit-bookmark--make-record 'magit-revision-mode #'magit-bookmark--revision-jump (lambda (_rev _ args files) `((defaults . (,(magit-bookmark--revision-make-name (buffer-name) magit-buffer-revision-hash args files))) (magit-rev . ,magit-buffer-revision-hash) (magit-args . ,args) (magit-files . ,files))))) ;;; Stash ;;;###autoload (defun magit-bookmark--stash-jump (bookmark) "Handle a Magit stash BOOKMARK." (magit-bookmark--jump bookmark #'magit-stash-show (bookmark-prop-get bookmark 'magit-stash) (bookmark-prop-get bookmark 'magit-args) (bookmark-prop-get bookmark 'magit-files))) (defun magit-bookmark--stash-make-name (buffer-name stash _args files) "Generate the default name for a stash bookmark." (concat buffer-name " " stash " " (if files (mapconcat #'identity files " ") (magit-rev-format "%s" stash)))) ;;;###autoload (defun magit-bookmark--stash-make-record () "Create a Magit stash bookmark." (magit-bookmark--make-record 'magit-stash-mode #'magit-bookmark--stash-jump (lambda (stash _ args files) `((defaults . (,(magit-bookmark--stash-make-name (buffer-name) (magit-rev-abbrev magit-buffer-revision-hash) args files))) (magit-stash . ,magit-buffer-revision-hash) (magit-args . ,args) (magit-files . ,files) (magit-hidden-sections . ,(--map `(,(oref it type) . ,(replace-regexp-in-string (regexp-quote stash) magit-buffer-revision-hash (oref it value))) (--filter (oref it hidden) (oref magit-root-section children)))))))) ;;; Submodules ;;;###autoload (defun magit-bookmark--submodules-jump (bookmark) "Handle a Magit submodule list BOOKMARK." (magit-bookmark--jump bookmark #'magit-list-submodules)) ;;;###autoload (defun magit-bookmark--submodules-make-record () "Create a Magit submodule list bookmark." (magit-bookmark--make-record 'magit-submodule-list-mode #'magit-bookmark--submodules-jump)) (provide 'magit-bookmark) ;;; magit-bookmark.el ends here