diff options
| author | Augusto Stoffel | 2022-09-08 11:09:42 +0200 |
|---|---|---|
| committer | Lars Ingebrigtsen | 2022-09-14 21:58:04 +0200 |
| commit | a9941269683fe50673d0aa81feefb7a9d3d8a6b9 (patch) | |
| tree | 566c9ecd3afb90b58607c71ad794cee14ba7b823 /lisp/pcomplete.el | |
| parent | 05971d2b8d47e69e9585d0d6066b8a607555aa48 (diff) | |
| download | emacs-a9941269683fe50673d0aa81feefb7a9d3d8a6b9.tar.gz emacs-a9941269683fe50673d0aa81feefb7a9d3d8a6b9.zip | |
pcomplete: Generate completions from --help messages
* lisp/pcomplete.el (pcomplete-from-help): New function (and hash
table) to get pcomplete candidates from help messages.
(pcomplete-here-using-help): Helper function to define pcomplete for
simple commands
(pcomplete-completions-at-point): Provide annotation-function and
company-docsig properties.
* lisp/pcmpl-git.el: New file, provides pcomplete for Git.
* lisp/pcmpl-gnu.el: Add pcomplete for awk, gpg and gdb, emacs and
emacsclient.
* lisp/pcmpl-linux.el: Add pcomplete for systemctl and journalctl.
* lisp/pcmpl-rpm.el: Add pcomplete for dnf.
* lisp/pcmpl-unix.el: Add pcomplete for sudo and most commands found
in GNU Coreutils.
* lisp/pcmpl-x.el: Add pcomplete for tex, pdftex, latex, pdflatex,
rigrep and rclone.
* test/lisp/pcomplete-tests.el (pcomplete-test-parse-gpg-help,
pcomplete-test-parse-git-help): Tests for the new functions.
Diffstat (limited to 'lisp/pcomplete.el')
| -rw-r--r-- | lisp/pcomplete.el | 138 |
1 files changed, 138 insertions, 0 deletions
diff --git a/lisp/pcomplete.el b/lisp/pcomplete.el index 0e3d1df7814..6fe29d9dcfb 100644 --- a/lisp/pcomplete.el +++ b/lisp/pcomplete.el | |||
| @@ -119,6 +119,9 @@ | |||
| 119 | ;;; Code: | 119 | ;;; Code: |
| 120 | 120 | ||
| 121 | (require 'comint) | 121 | (require 'comint) |
| 122 | (eval-when-compile | ||
| 123 | (require 'cl-lib) | ||
| 124 | (require 'rx)) | ||
| 122 | 125 | ||
| 123 | (defgroup pcomplete nil | 126 | (defgroup pcomplete nil |
| 124 | "Programmable completion." | 127 | "Programmable completion." |
| @@ -481,6 +484,14 @@ Same as `pcomplete' but using the standard completion UI." | |||
| 481 | (when completion-ignore-case | 484 | (when completion-ignore-case |
| 482 | (setq table (completion-table-case-fold table))) | 485 | (setq table (completion-table-case-fold table))) |
| 483 | (list beg (point) table | 486 | (list beg (point) table |
| 487 | :annotation-function | ||
| 488 | (lambda (cand) | ||
| 489 | (when (stringp cand) | ||
| 490 | (get-text-property 0 'pcomplete-annotation cand))) | ||
| 491 | :company-docsig | ||
| 492 | (lambda (cand) | ||
| 493 | (when (stringp cand) | ||
| 494 | (get-text-property 0 'pcomplete-help cand))) | ||
| 484 | :predicate pred | 495 | :predicate pred |
| 485 | :exit-function | 496 | :exit-function |
| 486 | ;; If completion is finished, add a terminating space. | 497 | ;; If completion is finished, add a terminating space. |
| @@ -1325,6 +1336,133 @@ If specific documentation can't be given, be generic." | |||
| 1325 | (pcomplete-read-hosts pcomplete-hosts-file 'pcomplete--host-name-cache | 1336 | (pcomplete-read-hosts pcomplete-hosts-file 'pcomplete--host-name-cache |
| 1326 | 'pcomplete--host-name-cache-timestamp))) | 1337 | 'pcomplete--host-name-cache-timestamp))) |
| 1327 | 1338 | ||
| 1339 | ;;; Parsing help messages | ||
| 1340 | |||
| 1341 | (defvar pcomplete-from-help (make-hash-table :test #'equal) | ||
| 1342 | "Memoization table for function `pcomplete-from-help'.") | ||
| 1343 | |||
| 1344 | (cl-defun pcomplete-from-help (command | ||
| 1345 | &rest args | ||
| 1346 | &key | ||
| 1347 | (margin (rx bol (+ " "))) | ||
| 1348 | (argument (rx "-" (+ (any "-" alnum)) (? "="))) | ||
| 1349 | (metavar (rx (? " ") | ||
| 1350 | (or (+ (any alnum "_-")) | ||
| 1351 | (seq "[" (+? nonl) "]") | ||
| 1352 | (seq "<" (+? nonl) ">") | ||
| 1353 | (seq "{" (+? nonl) "}")))) | ||
| 1354 | (separator (rx ", " symbol-start)) | ||
| 1355 | (description (rx (* nonl) | ||
| 1356 | (* "\n" (>= 9 " ") (* nonl)))) | ||
| 1357 | narrow-start | ||
| 1358 | narrow-end) | ||
| 1359 | "Parse output of COMMAND into a list of completion candidates. | ||
| 1360 | |||
| 1361 | COMMAND can be a string to be executed in a shell or a list of | ||
| 1362 | strings (program name and arguments). It should print a help | ||
| 1363 | message. | ||
| 1364 | |||
| 1365 | A list of arguments is collected after each match of MARGIN. | ||
| 1366 | Each argument should match ARGUMENT, possibly followed by a match | ||
| 1367 | of METAVAR. If a match of SEPARATOR follows, then more | ||
| 1368 | argument-metavar pairs are collected. Finally, a match of | ||
| 1369 | DESCRIPTION is collected. | ||
| 1370 | |||
| 1371 | Keyword ARGS: | ||
| 1372 | |||
| 1373 | MARGIN: regular expression after which argument descriptions are | ||
| 1374 | to be found. Parsing continues at the end of the first match | ||
| 1375 | group or, failing that, the entire match. | ||
| 1376 | |||
| 1377 | ARGUMENT: regular expression matching an argument name. The | ||
| 1378 | first match group (failing that, the entire match) is collected | ||
| 1379 | as the argument name. Parsing continues at the end of the | ||
| 1380 | second matching group (failing that, the first group or entire | ||
| 1381 | match). | ||
| 1382 | |||
| 1383 | METAVAR: regular expression matching an argument parameter name. | ||
| 1384 | The first match group (failing that, the entire match) is | ||
| 1385 | collected as the parameter name and used as completion | ||
| 1386 | annotation. Parsing continues at the end of the second | ||
| 1387 | matching group (failing that, the first group or entire match). | ||
| 1388 | |||
| 1389 | SEPARATOR: regular expression matching the separator between | ||
| 1390 | arguments. Parsing continues at the end of the first match | ||
| 1391 | group (failing that, the entire match). | ||
| 1392 | |||
| 1393 | DESCRIPTION: regular expression matching the description of an | ||
| 1394 | argument. The first match group (failing that, the entire | ||
| 1395 | match) is collected as the parameter name and used as | ||
| 1396 | completion help. Parsing continues at the end of the first | ||
| 1397 | matching group (failing that, the entire match). | ||
| 1398 | |||
| 1399 | NARROW-START, NARROW-END: if non-nil, parsing of the help message | ||
| 1400 | is narrowed to the region between the end of the first match | ||
| 1401 | group (failing that, the entire match) of these regular | ||
| 1402 | expressions." | ||
| 1403 | (with-memoization (gethash (cons command args) pcomplete-from-help) | ||
| 1404 | (with-temp-buffer | ||
| 1405 | (let ((case-fold-search nil) | ||
| 1406 | (default-directory (expand-file-name "~/")) | ||
| 1407 | (command (if (stringp command) | ||
| 1408 | (list shell-file-name | ||
| 1409 | shell-command-switch | ||
| 1410 | command) | ||
| 1411 | command)) | ||
| 1412 | i result) | ||
| 1413 | (apply #'call-process (car command) nil t nil (cdr command)) | ||
| 1414 | (goto-char (point-min)) | ||
| 1415 | (narrow-to-region (or (and narrow-start | ||
| 1416 | (re-search-forward narrow-start nil t) | ||
| 1417 | (or (match-beginning 1) (match-beginning 0))) | ||
| 1418 | (point-min)) | ||
| 1419 | (or (and narrow-end | ||
| 1420 | (re-search-forward narrow-end nil t) | ||
| 1421 | (or (match-beginning 1) (match-beginning 0))) | ||
| 1422 | (point-max))) | ||
| 1423 | (goto-char (point-min)) | ||
| 1424 | (while (re-search-forward margin nil t) | ||
| 1425 | (goto-char (or (match-end 1) (match-end 0))) | ||
| 1426 | (setq i 0) | ||
| 1427 | (while (and (or (zerop i) | ||
| 1428 | (and (looking-at separator) | ||
| 1429 | (goto-char (or (match-end 1) | ||
| 1430 | (match-end 0))))) | ||
| 1431 | (looking-at argument)) | ||
| 1432 | (setq i (1+ i)) | ||
| 1433 | (goto-char (seq-some #'match-end '(2 1 0))) | ||
| 1434 | (push (or (match-string 1) (match-string 0)) result) | ||
| 1435 | (when (looking-at metavar) | ||
| 1436 | (goto-char (seq-some #'match-end '(2 1 0))) | ||
| 1437 | (put-text-property 0 1 | ||
| 1438 | 'pcomplete-annotation | ||
| 1439 | (or (match-string 1) (match-string 0)) | ||
| 1440 | (car result)))) | ||
| 1441 | (when (looking-at description) | ||
| 1442 | (goto-char (seq-some #'match-end '(2 1 0))) | ||
| 1443 | (let ((help (string-clean-whitespace | ||
| 1444 | (or (match-string 1) (match-string 0)))) | ||
| 1445 | (items (take i result))) | ||
| 1446 | (while items | ||
| 1447 | (put-text-property 0 1 'pcomplete-help help | ||
| 1448 | (pop items)))))) | ||
| 1449 | (nreverse result))))) | ||
| 1450 | |||
| 1451 | (defun pcomplete-here-using-help (command &rest args) | ||
| 1452 | "Perform completion for a simple command. | ||
| 1453 | Offer switches and directory entries as completion candidates. | ||
| 1454 | The switches are obtained by calling `pcomplete-from-help' with | ||
| 1455 | COMMAND and ARGS as arguments." | ||
| 1456 | (while (cond | ||
| 1457 | ((string= "--" (pcomplete-arg 1)) | ||
| 1458 | (while (pcomplete-here (pcomplete-entries)))) | ||
| 1459 | ((pcomplete-match "\\`--[^=]+=\\(.*\\)" 0) | ||
| 1460 | (pcomplete-here (pcomplete-entries) | ||
| 1461 | (pcomplete-match-string 1 0))) | ||
| 1462 | ((string-prefix-p "-" (pcomplete-arg 0)) | ||
| 1463 | (pcomplete-here (apply #'pcomplete-from-help command args))) | ||
| 1464 | (t (pcomplete-here (pcomplete-entries)))))) | ||
| 1465 | |||
| 1328 | (provide 'pcomplete) | 1466 | (provide 'pcomplete) |
| 1329 | 1467 | ||
| 1330 | ;;; pcomplete.el ends here | 1468 | ;;; pcomplete.el ends here |