emacs/lisp/pcmpl-git.el

111 lines
4.7 KiB
EmacsLisp

;;; pcmpl-git.el --- Completions for Git -*- lexical-binding: t -*-
;; Copyright (C) 2022-2024 Free Software Foundation, Inc.
;; Package: pcomplete
;; This file is part of GNU Emacs.
;; GNU Emacs 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 of the License, or
;; (at your option) any later version.
;; GNU Emacs 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 GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;; This library provides completion rules for the Git program.
;;; Code:
(require 'pcomplete)
(require 'vc-git)
(defun pcmpl-git--expand-flags (args)
"In the list of ARGS, expand arguments of the form --[no-]flag."
(mapcan (lambda (arg) (if (string-search "[no-]" arg)
(list (string-replace "[no-]" "" arg)
(string-replace "[no-]" "no-" arg))
(list arg)))
args))
(defun pcmpl-git--tracked-file-predicate (&rest args)
"Return a predicate function determining the Git status of a file.
Files listed by `git ls-files ARGS' satisfy the predicate."
(when-let ((files (mapcar #'expand-file-name
(ignore-errors
(apply #'process-lines
vc-git-program "ls-files" args)))))
(lambda (file)
(setq file (expand-file-name file))
(if (string-suffix-p "/" file)
(seq-some (lambda (f) (string-prefix-p file f))
files)
(member file files)))))
(defun pcmpl-git--remote-refs (remote)
"List the locally known Git revisions from REMOTE."
(delq nil
(mapcar
(let ((re (concat "\\`" (regexp-quote remote) "/\\(.*\\)")))
(lambda (s) (when (string-match re s) (match-string 1 s))))
(vc-git-revision-table nil))))
;;;###autoload
(defun pcomplete/git ()
"Completion for the `git' command."
(let ((subcommands (pcomplete-from-help `(,vc-git-program "help" "-a")
:margin "^\\( +\\)[a-z]"
:argument "[[:alnum:]-]+")))
(while (not (member (pcomplete-arg 1) subcommands))
(if (string-prefix-p "-" (pcomplete-arg))
(pcomplete-here (pcomplete-from-help `(,vc-git-program "help")
:margin "\\(\\[\\)-"
:separator " | "
:description "\\`"))
(pcomplete-here (completion-table-merge
subcommands
(when (string-prefix-p "-" (pcomplete-arg 1))
(pcomplete-entries))))))
(let ((subcmd (pcomplete-arg 1)))
(while (pcase subcmd
((guard (string-prefix-p "-" (pcomplete-arg)))
(pcomplete-here
(pcmpl-git--expand-flags
(pcomplete-from-help `(,vc-git-program "help" ,subcmd)
:argument
"-+\\(?:\\[no-\\]\\)?[a-z-]+=?"))))
;; Complete modified tracked files
((or "add" "commit" "restore")
(pcomplete-here
(pcomplete-entries
nil (pcmpl-git--tracked-file-predicate "-m"))))
;; Complete all tracked files
((or "mv" "rm" "grep" "status" "blame")
(pcomplete-here
(pcomplete-entries nil (pcmpl-git--tracked-file-predicate))))
;; Complete revisions
((or "branch" "merge" "rebase" "switch")
(pcomplete-here (vc-git-revision-table nil)))
;; Complete revisions and tracked files
;; TODO: diff and log accept revision ranges
((or "checkout" "reset" "show" "diff" "log")
(pcomplete-here
(completion-table-in-turn
(vc-git-revision-table nil)
(pcomplete-entries nil (pcmpl-git--tracked-file-predicate)))))
;; Complete remotes and their revisions
((or "fetch" "pull" "push")
(pcomplete-here (process-lines vc-git-program "remote"))
(pcomplete-here (pcmpl-git--remote-refs (pcomplete-arg 1)))))))))
(provide 'pcmpl-git)
;;; pcmpl-git.el ends here