Skip to content

Commit

Permalink
Support visiting blobs using a browser
Browse files Browse the repository at this point in the history
  • Loading branch information
tarsius committed Jan 8, 2025
1 parent 0c81b44 commit 13dd90e
Show file tree
Hide file tree
Showing 11 changed files with 136 additions and 10 deletions.
18 changes: 17 additions & 1 deletion docs/forge.org
Original file line number Diff line number Diff line change
Expand Up @@ -869,7 +869,7 @@ argument, i.e., ~C-u RET~.
- Key: o [on repository in repository list] (forge-browse-this-repository) ::

These commands visit the topic, issue(s), pull-request(s), post,
branch, commit, remote or repository at point in a browser.
branch, commit, remote, repository or blob at point in a browser.

- Command: forge-browse-commit ::
- Command: forge-browse-branch ::
Expand All @@ -884,6 +884,22 @@ argument, i.e., ~C-u RET~.
These commands read a topic, issue(s), pull-request(s), branch,
commit, remote or repository, and open it in a browser.

- Command: forge-browse-commit ::

This command visit a blob in a browser.

When invoked from a blob- or file-visiting buffer, visit that blob
without prompting. If the region is active, try to jump to the marked
line or lines, and highlight them in the browser. To what extend that
is possible depends on the forge. When the region is not active just
visit the blob, without trying to jump to the current line. When
jumping to a line, always use a commit hash as part of the URL. From
a file in the worktree with no active region, instead use the branch
name as part of the URL, unless a prefix argument is used.

When invoked from any other buffer, prompt the user for a branch or
commit, and for a file.

* Creating Topics and Posts

We call both issues and pull-requests "topics". The contributions to
Expand Down
18 changes: 17 additions & 1 deletion docs/forge.texi
Original file line number Diff line number Diff line change
Expand Up @@ -996,7 +996,7 @@ separate buffer.
@findex forge-browse-this-topic
@findex forge-browse-this-repository
These commands visit the topic, issue(s), pull-request(s), post,
branch, commit, remote or repository at point in a browser.
branch, commit, remote, repository or blob at point in a browser.
@end table

@deffn Command forge-browse-commit
Expand Down Expand Up @@ -1028,6 +1028,22 @@ These commands read a topic, issue(s), pull-request(s), branch,
commit, remote or repository, and open it in a browser.
@end table

@deffn Command forge-browse-commit
This command visit a blob in a browser.

When invoked from a blob- or file-visiting buffer, visit that blob
without prompting. If the region is active, try to jump to the marked
line or lines, and highlight them in the browser. To what extend that
is possible depends on the forge. When the region is not active just
visit the blob, without trying to jump to the current line. When
jumping to a line, always use a commit hash as part of the URL@. From
a file in the worktree with no active region, instead use the branch
name as part of the URL, unless a prefix argument is used.

When invoked from any other buffer, prompt the user for a branch or
commit, and for a file.
@end deffn

@node Creating Topics and Posts
@chapter Creating Topics and Posts

Expand Down
1 change: 1 addition & 0 deletions lisp/forge-bitbucket.el
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
(commit-url-format :initform "https://%h/%o/%n/commits/%r")
(branch-url-format :initform "https://%h/%o/%n/branch/%r")
(remote-url-format :initform "https://%h/%o/%n/src")
(blob-url-format :initform "https://%h/%o/%n/src/%r/%f")
(create-issue-url-format :initform "https://%h/%o/%n/issues/new")
(create-pullreq-url-format :initform "https://%h/%o/%n/pull-requests/new")))

Expand Down
78 changes: 77 additions & 1 deletion lisp/forge-commands.el
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,24 @@ argument also offer closed pull-requests."
(interactive (list (forge-read-repository "Browse repository")))
(browse-url (forge-get-url repository)))

;;;###autoload
(defun forge-browse-blob (commit file &optional line end force-hash)
"Visit a blob using a browser.
When invoked from a blob- or file-visiting buffer, visit that blob
without prompting. If the region is active, try to jump to the marked
line or lines, and highlight them in the browser. To what extend that
is possible depends on the forge. When the region is not active just
visit the blob, without trying to jump to the current line. When
jumping to a line, always use a commit hash as part of the URL. From
a file in the worktree with no active region, instead use the branch
name as part of the URL, unless a prefix argument is used.
When invoked from any other buffer, prompt the user for a branch or
commit, and for a file."
(interactive (forge--browse-blob-args))
(browse-url (forge-get-url :blob commit file line end force-hash)))

;;;###autoload(autoload 'forge-browse-this-topic "forge-commands" nil t)
(transient-define-suffix forge-browse-this-topic ()
"Visit the topic at point using a browser."
Expand All @@ -308,7 +326,7 @@ argument also offer closed pull-requests."

;;;###autoload
(defun forge-copy-url-at-point-as-kill ()
"Copy the url of the thing at point."
"Copy the url of thing at point or the thing visited in the current buffer."
(interactive)
(if-let ((target (forge--browse-target)))
(let ((url (if (stringp target) target (forge-get-url target))))
Expand Down Expand Up @@ -337,12 +355,32 @@ argument also offer closed pull-requests."
(forge-get-url :branch branch))
(and-let* ((remote (magit-remote-at-point)))
(forge-get-url :remote remote))
(and-let* ((file (magit-file-at-point)))
(forge-get-url :blob nil file))
(forge-post-at-point)
(forge-current-topic)
(and (or magit-buffer-file-name buffer-file-name)
(apply #'forge-get-url :blob (forge--browse-blob-args)))
(and magit-buffer-revision
(forge-get-url :commit magit-buffer-revision))
(forge-get-repository :stub?)))

(defun forge--browse-blob-args ()
(cond
(magit-buffer-file-name
`(,(or magit-buffer-refname magit-buffer-revision)
,(magit-file-relative-name magit-buffer-file-name)
,@(magit-file-region-line-numbers)
,current-prefix-arg))
(buffer-file-name
`(nil
,(magit-file-relative-name buffer-file-name)
,@(magit-file-region-line-numbers)
,current-prefix-arg))
((let ((commit (magit-read-local-branch-or-commit
"Browse file from commit")))
(list commit (magit-read-file-from-rev commit "Browse file"))))))

;;;; Urls

(cl-defgeneric forge-get-url (obj)
Expand All @@ -369,6 +407,23 @@ argument also offer closed pull-requests."
(forge--format repo 'commit-url-format
`((?r . ,(magit-commit-p commit))))))

(cl-defmethod forge-get-url ((_(eql :blob)) commit file
&optional line end force-hash)
(let* ((commit (or (and (magit-branch-p commit)
(cdr (magit-split-branch-name commit)))
(and commit (magit-commit-p commit))
(and (not (or line force-hash))
(magit-get-current-branch))
(magit-rev-parse "HEAD")))
(repo (forge-get-repository :stub))
(format (oref repo blob-url-format)))
(when (cl-typep repo 'forge-gitweb-repository)
(setq commit (concat (if (magit-branch-p commit) "hb=" "h=") commit)))
(concat
(forge--format repo format `((?r . ,commit) (?f . ,file)))
(and line (forge-format-blob-lines repo line
(and (not (equal line end)) end))))))

(cl-defmethod forge-get-url ((_(eql :branch)) branch)
(let (remote)
(if (magit-remote-branch-p branch)
Expand All @@ -393,6 +448,27 @@ argument also offer closed pull-requests."
(cl-defmethod forge-get-url ((notify forge-notification))
(oref notify url))

(cl-defmethod forge-format-blob-lines ((repo forge-repository) line end)
(cl-etypecase repo ;Third-party classes require separate methods.
((or forge-github-repository
forge-gitlab-repository ;Also supports "#L%s-%s".
forge-forgejo-repository
forge-gitea-repository
forge-gogs-repository)
(format (if end "#L%s-L%s" "#L%s") line end))
(forge-bitbucket-repository
(format (if end "#lines-%s:%s" "#lines-%s") line end))
((or forge-cgit-repository
forge-cgit*-repository
forge-cgit**-repository)
(format "#n%s" line))
((or forge-gitweb-repository
forge-repoorcz-repository
forge-stagit-repository)
(format "#l%s" line))
(forge-srht-repository
(format "#L%s" line))))

;;; Visit

;;;###autoload
Expand Down
1 change: 1 addition & 0 deletions lisp/forge-forgejo.el
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
(commit-url-format :initform "https://%h/%o/%n/commit/%r")
(branch-url-format :initform "https://%h/%o/%n/commits/branch/%r")
(remote-url-format :initform "https://%h/%o/%n")
(blob-url-format :initform "https://%h/%o/%n/src/%r/%f")
(create-issue-url-format :initform "https://%h/%o/%n/issues/new")
(create-pullreq-url-format :initform "https://%h/%o/%n/pulls") ; sic
(pullreq-refspec :initform "+refs/pull/*/head:refs/pullreqs/*")))
Expand Down
1 change: 1 addition & 0 deletions lisp/forge-gitea.el
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
(commit-url-format :initform "https://%h/%o/%n/commit/%r")
(branch-url-format :initform "https://%h/%o/%n/commits/branch/%r")
(remote-url-format :initform "https://%h/%o/%n")
(blob-url-format :initform "https://%h/%o/%n/src/%r/%f")
(create-issue-url-format :initform "https://%h/%o/%n/issues/new")
(create-pullreq-url-format :initform "https://%h/%o/%n/pulls") ; sic
(pullreq-refspec :initform "+refs/pull/*/head:refs/pullreqs/*")))
Expand Down
1 change: 1 addition & 0 deletions lisp/forge-github.el
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
(commit-url-format :initform "https://%h/%o/%n/commit/%r")
(branch-url-format :initform "https://%h/%o/%n/commits/%r")
(remote-url-format :initform "https://%h/%o/%n")
(blob-url-format :initform "https://%h/%o/%n/blob/%r/%f")
(create-issue-url-format :initform "https://%h/%o/%n/issues/new")
(create-pullreq-url-format :initform "https://%h/%o/%n/compare")
(pullreq-refspec :initform "+refs/pull/*/head:refs/pullreqs/*")))
Expand Down
1 change: 1 addition & 0 deletions lisp/forge-gitlab.el
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
(commit-url-format :initform "https://%h/%o/%n/commit/%r")
(branch-url-format :initform "https://%h/%o/%n/commits/%r")
(remote-url-format :initform "https://%h/%o/%n")
(blob-url-format :initform "https://%h/%o/%n/-/blob/%r/%f")
(create-issue-url-format :initform "https://%h/%o/%n/issues/new")
(create-pullreq-url-format :initform "https://%h/%o/%n/merge_requests/new")
(pullreq-refspec :initform "+refs/merge-requests/*/head:refs/pullreqs/*")))
Expand Down
1 change: 1 addition & 0 deletions lisp/forge-gogs.el
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
(commit-url-format :initform "https://%h/%o/%n/commit/%r")
(branch-url-format :initform "https://%h/%o/%n/commits/%r")
(remote-url-format :initform "https://%h/%o/%n")
(blob-url-format :initform "https://%h/%o/%n/src/%r/%f")
(create-issue-url-format :initform "https://%h/%o/%n/issues/new")
(create-pullreq-url-format :initform "https://%h/%o/%n/pulls") ; sic
(pullreq-refspec :initform "+refs/pull/*/head:refs/pullreqs/*")))
Expand Down
1 change: 1 addition & 0 deletions lisp/forge-repo.el
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
(commit-url-format :initform nil :allocation :class)
(branch-url-format :initform nil :allocation :class)
(remote-url-format :initform nil :allocation :class)
(blob-url-format :initform nil :allocation :class)
(create-issue-url-format :initform nil :allocation :class)
(create-pullreq-url-format :initform nil :allocation :class)
(pullreq-refspec :initform nil :allocation :class)
Expand Down
25 changes: 18 additions & 7 deletions lisp/forge-semi.el
Original file line number Diff line number Diff line change
Expand Up @@ -29,53 +29,64 @@
(defclass forge-gitweb-repository (forge-noapi-repository)
((commit-url-format :initform "https://%h/gitweb/?p=%P.git;a=commitdiff;h=%r")
(branch-url-format :initform "https://%h/gitweb/?p=%P.git;a=log;h=refs/heads/%r")
(remote-url-format :initform "https://%h/gitweb/?p=%P.git;a=summary"))
(remote-url-format :initform "https://%h/gitweb/?p=%P.git;a=summary")
;; We must use "hb=BRANCH" because "h=refs/heads/BRANCH" does not work
;; here. So "%r" stands for either "hb=BRANCH" or "h=HASH" and which
;; it is, has to be handled as a special case in `forge-get-url(:blob)'.
(blob-url-format :initform "https://%h/gitweb/?p=%P.git;a=blob;f=%s;%r"))
"Gitweb from https://git-scm.com/docs/gitweb.")

(defclass forge-cgit-repository (forge-noapi-repository)
((commit-url-format :initform "https://%h/%p.git/commit/?id=%r")
(branch-url-format :initform "https://%h/%p.git/log/?h=%r")
(remote-url-format :initform "https://%h/%p.git/about"))
(remote-url-format :initform "https://%h/%p.git/about")
(blob-url-format :initform "https://%h/%p.git/tree/%f?id=%r"))
"Cgit from https://git.zx2c4.com/cgit/about.
Different hosts use different url schemata, so we need multiple
classes. See their definitions in \"forge-semi.el\".")

(defclass forge-cgit*-repository (forge-cgit-repository)
((commit-url-format :initform "https://%h/cgit/%p.git/commit/?id=%r")
(branch-url-format :initform "https://%h/cgit/%p.git/log/?h=%r")
(remote-url-format :initform "https://%h/cgit/%p.git/about"))
(remote-url-format :initform "https://%h/cgit/%p.git/about")
(blob-url-format :initform "https://%h/cgit/%p.git/tree/%f?id=%r"))
"Cgit from https://git.zx2c4.com/cgit/about.
Different hosts use different url schemata, so we need multiple
classes. See their definitions in \"forge-semi.el\".")

(defclass forge-cgit**-repository (forge-cgit-repository)
((commit-url-format :initform "https://%h/cgit/%n.git/commit/?id=%r")
(branch-url-format :initform "https://%h/cgit/%n.git/log/?h=%r")
(remote-url-format :initform "https://%h/cgit/%n.git/about"))
(remote-url-format :initform "https://%h/cgit/%n.git/about")
(blob-url-format :initform "https://%h/cgit/%n.git/tree/%f?id=%r"))
"Cgit from https://git.zx2c4.com/cgit/about.
Different hosts use different url schemata, so we need multiple
classes. See their definitions in \"forge-semi.el\".")

(defclass forge-repoorcz-repository (forge-cgit-repository)
((commit-url-format :initform "https://%h/%p.git/commit/%r")
(branch-url-format :initform "https://%h/%p.git/log/%r")
(remote-url-format :initform "https://%h/%p.git"))
(remote-url-format :initform "https://%h/%p.git")
(blob-url-format :initform "https://%h/%p.git/blob/%r:/%f"))
"Cgit fork used on https://repo.or.cz/cgit.git.
Different hosts use different url schemata, so we need multiple
classes. See their definitions in \"forge-semi.el\".")

(defclass forge-stagit-repository (forge-noapi-repository)
((commit-url-format :initform "https://%h/%n/commit/%r.html")
(branch-url-format :initform "https://%h/%n/refs.html")
(remote-url-format :initform "https://%h/%n/file/README.html"))
(remote-url-format :initform "https://%h/%n/file/README.html")
;; Can only link to the tip of the main branch.
(blob-url-format :initform "https://%h/%n/"))
"Stagit from https://codemadness.org/git/stagit/file/README.html.
Only the history of \"master\" can be shown, so this links to the
list of refs instead of the log of the specified branch.")

(defclass forge-srht-repository (forge-noapi-repository)
((commit-url-format :initform "https://%h/~%o/%n/commit/%r")
(branch-url-format :initform "https://%h/~%o/%n/log/%r")
(remote-url-format :initform "https://%h/~%o/%n"))
(remote-url-format :initform "https://%h/~%o/%n")
(blob-url-format :initform "https://%h/~%o/%n/tree/%r/item/%f"))
"See https://meta.sr.ht.")

;;; _
Expand Down

0 comments on commit 13dd90e

Please sign in to comment.