Personal emacs config
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

176 lines
6.0 KiB

  1. ;;; pip-requirements.el --- A major mode for editing pip requirements files.
  2. ;; Copyright (C) 2014 Wilfred Hughes <me@wilfred.me.uk>
  3. ;;
  4. ;; Author: Wilfred Hughes <me@wilfred.me.uk>
  5. ;; Created: 11 September 2014
  6. ;; Version: 0.6
  7. ;; Package-Version: 20181027.1629
  8. ;; Package-Commit: 216cd1690f80cc965d4ae47b8753fc185f778ff6
  9. ;; Package-Requires: ((dash "2.8.0"))
  10. ;;; License:
  11. ;; This file is not part of GNU Emacs.
  12. ;; However, it is distributed under the same license.
  13. ;; GNU Emacs is free software; you can redistribute it and/or modify
  14. ;; it under the terms of the GNU General Public License as published by
  15. ;; the Free Software Foundation; either version 3, or (at your option)
  16. ;; any later version.
  17. ;; GNU Emacs is distributed in the hope that it will be useful,
  18. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  19. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  20. ;; GNU General Public License for more details.
  21. ;; You should have received a copy of the GNU General Public License
  22. ;; along with GNU Emacs; see the file COPYING. If not, write to the
  23. ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
  24. ;; Boston, MA 02110-1301, USA.
  25. ;;; Commentary:
  26. ;; This is a major mode for editing pip requirements files, with the following features:
  27. ;; * Syntax highlighting
  28. ;; * Togglable comments
  29. ;; * Auto completion of package names from PyPI
  30. ;; TODO: Steal shamelessly all the fantasic ideas in
  31. ;; https://github.com/wuub/requirementstxt
  32. ;;; Code:
  33. (require 'dash)
  34. (require 'cl-lib)
  35. (defgroup pip-requirements nil
  36. "Requirements files for pip"
  37. :prefix "pip-requirements-"
  38. :group 'languages
  39. :link '(url-link :tag "Github" "https://github.com/Wilfred/pip-requirements.el"))
  40. (defcustom pip-requirements-mode-hook nil
  41. "Hook to run after `pip-requirements-mode'."
  42. :group 'pip-requirements
  43. :type 'hook
  44. :risky t)
  45. (defcustom pip-requirements-index-url
  46. "https://pypi.org/simple/"
  47. "The URL used to fetch the list of packages used for completion."
  48. :group 'pip-requirements
  49. :type 'string)
  50. ;;;###autoload
  51. (add-to-list 'auto-mode-alist
  52. `(,(rx ".pip" string-end) . pip-requirements-mode))
  53. ;;;###autoload
  54. (add-to-list 'auto-mode-alist
  55. `(,(rx "requirements" (zero-or-more anything) ".txt" string-end) . pip-requirements-mode))
  56. ;;;###autoload
  57. (add-to-list 'auto-mode-alist
  58. `(,(rx "requirements.in") . pip-requirements-mode))
  59. (defconst pip-requirements-name-regex
  60. (rx
  61. line-start
  62. (group (1+ (or alphanumeric "-" "_" ".")))))
  63. (defconst pip-requirements-version-regex
  64. ;; https://www.python.org/dev/peps/pep-0440/#version-specifiers
  65. (rx
  66. (group (or "==" ">" ">=" "<" "<=" "!=" "~="))
  67. (group (1+ (or digit "b" "." "post" "*")))))
  68. (defconst pip-requirements-arbitrary-version-regex
  69. ;; https://www.python.org/dev/peps/pep-0440/#arbitrary-equality
  70. (rx (group "===") (group (1+ not-newline))))
  71. (defconst pip-requirements-operators
  72. (list
  73. (list pip-requirements-name-regex 1 'font-lock-variable-name-face)
  74. (list pip-requirements-version-regex 1 'font-lock-builtin-face)
  75. (list pip-requirements-arbitrary-version-regex 1 'font-lock-builtin-face)
  76. (list pip-requirements-version-regex 2 'font-lock-constant-face)
  77. (list pip-requirements-arbitrary-version-regex 2 'font-lock-constant-face)))
  78. (defconst pip-requirements-syntax-table
  79. (let ((table (make-syntax-table)))
  80. (modify-syntax-entry ?# "<" table)
  81. (modify-syntax-entry ?\n ">" table)
  82. (modify-syntax-entry ?> "." table)
  83. (modify-syntax-entry ?< "." table)
  84. (modify-syntax-entry ?= "." table)
  85. (modify-syntax-entry ?~ "." table)
  86. table))
  87. (defvar pip-http-buffer nil)
  88. (defvar pip-packages nil
  89. "List of PyPI packages for completion.")
  90. (defun pip-requirements-callback (&rest _)
  91. (with-current-buffer pip-http-buffer
  92. ;; Move over the HTTP header.
  93. (goto-char (point-min))
  94. (re-search-forward "^$" nil 'move)
  95. (let* ((dom (libxml-parse-html-region (point) (point-max)))
  96. (body-tag (-last-item dom))
  97. (body-children (cdddr body-tag))
  98. (a-tags (--filter (eq (car-safe it) 'a) body-children)))
  99. (setq pip-packages
  100. ;; Inner text of anchor tags.
  101. (-map #'cl-third a-tags))))
  102. (kill-buffer pip-http-buffer))
  103. (defun pip-requirements-fetch-packages ()
  104. "Get a list of all packages available on PyPI and store them in `pip-packages'.
  105. Assumes Emacs is compiled with libxml."
  106. (setq pip-http-buffer
  107. (url-retrieve pip-requirements-index-url
  108. #'pip-requirements-callback nil t)))
  109. (defun pip-requirements-complete-at-point ()
  110. "Complete at point in Pip Requirements Mode."
  111. (let* ((bounds (bounds-of-thing-at-point 'symbol))
  112. (start (or (car bounds) (point)))
  113. (end (or (cdr bounds) (point))))
  114. (list start end pip-packages)))
  115. ;; Declare variables from AC, to avoid a hard dependency on Auto Complete.
  116. (defvar ac-modes)
  117. (defvar ac-sources)
  118. ;;;###autoload
  119. (defun pip-requirements-auto-complete-setup ()
  120. "Setup Auto-Complete for Pip Requirements.
  121. See URL `https://github.com/auto-complete/auto-complete' for
  122. information about Auto Complete."
  123. (add-to-list 'ac-modes 'pip-requirements-mode)
  124. (add-to-list 'ac-sources '((candidates . pip-packages)))
  125. (when (and (fboundp 'auto-complete-mode)
  126. (not (bound-and-true-p auto-complete-mode)))
  127. ;; Enable Auto Complete
  128. (auto-complete-mode)))
  129. (custom-add-frequent-value 'pip-requirements-mode-hook
  130. 'pip-requirements-auto-complete-setup)
  131. ;;;###autoload
  132. (define-derived-mode pip-requirements-mode prog-mode "pip-require"
  133. "Major mode for editing pip requirements files."
  134. :syntax-table pip-requirements-syntax-table
  135. (set (make-local-variable 'font-lock-defaults) '(pip-requirements-operators))
  136. (set (make-local-variable 'comment-start) "#")
  137. (add-hook 'completion-at-point-functions
  138. #'pip-requirements-complete-at-point nil 'local)
  139. (unless pip-packages
  140. ;; Fetch the list of packages for completion
  141. (pip-requirements-fetch-packages)))
  142. (provide 'pip-requirements)
  143. ;;; pip-requirements.el ends here