| ;;; pylint.el --- minor mode for running `pylint' |
| |
| ;; Copyright (c) 2009, 2010 Ian Eure <[email protected]> |
| ;; Author: Ian Eure <[email protected]> |
| ;; Maintainer: Jonathan Kotta <[email protected]> |
| |
| ;; Keywords: languages python |
| ;; Version: 1.02 |
| |
| ;; pylint.el 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 2, or (at your option) any later |
| ;; version. |
| ;; |
| ;; It 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 your copy of Emacs; see the file COPYING. If not, write to the Free |
| ;; Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, |
| ;; MA 02110-1301, USA |
| |
| ;;; Commentary: |
| ;; |
| ;; Specialized compile mode for pylint. You may want to add the |
| ;; following to your init.el: |
| ;; |
| ;; (autoload 'pylint "pylint") |
| ;; (add-hook 'python-mode-hook 'pylint-add-menu-items) |
| ;; (add-hook 'python-mode-hook 'pylint-add-key-bindings) |
| ;; |
| ;; There is also a handy command `pylint-insert-ignore-comment' that |
| ;; makes it easy to insert comments of the form `# pylint: |
| ;; ignore=msg1,msg2,...'. |
| |
| ;;; Code: |
| |
| (require 'compile) |
| (require 'tramp) |
| |
| (defgroup pylint nil |
| "Minor mode for running the Pylint Python checker" |
| :prefix "pylint-" |
| :group 'tools |
| :group 'languages) |
| |
| (defvar pylint-last-buffer nil |
| "The most recent PYLINT buffer. |
| A PYLINT buffer becomes most recent when you select PYLINT mode in it. |
| Notice that using \\[next-error] or \\[compile-goto-error] modifies |
| `completion-last-buffer' rather than `pylint-last-buffer'.") |
| |
| (defconst pylint-regexp-alist |
| (let ((base "^\\(.*\\):\\([0-9]+\\):\s+\\(\\[%s.*\\)$")) |
| (list |
| (list (format base "[FE]") 1 2) |
| (list (format base "[RWC]") 1 2 nil 1))) |
| "Regexp used to match PYLINT hits. See `compilation-error-regexp-alist'.") |
| |
| (defcustom pylint-options '("--reports=n" "--output-format=parseable") |
| "Options to pass to pylint.py" |
| :type '(repeat string) |
| :group 'pylint) |
| |
| (defcustom pylint-use-python-indent-offset nil |
| "If non-nil, use `python-indent-offset' to set indent-string." |
| :type 'boolean |
| :group 'pylint) |
| |
| (defcustom pylint-command "pylint" |
| "PYLINT command." |
| :type '(file) |
| :group 'pylint) |
| |
| (defcustom pylint-alternate-pylint-command "pylint2" |
| "Command for pylint when invoked with C-u." |
| :type '(file) |
| :group 'pylint) |
| |
| (defcustom pylint-ask-about-save nil |
| "Non-nil means \\[pylint] asks which buffers to save before compiling. |
| Otherwise, it saves all modified buffers without asking." |
| :type 'boolean |
| :group 'pylint) |
| |
| (defvar pylint--messages-list () |
| "A list of strings of all pylint messages.") |
| |
| (defvar pylint--messages-list-hist () |
| "Completion history for `pylint--messages-list'.") |
| |
| (defun pylint--sort-messages (a b) |
| "Compare function for sorting `pylint--messages-list'. |
| |
| Sorts most recently used elements first using `pylint--messages-list-hist'." |
| (let ((idx 0) |
| (a-idx most-positive-fixnum) |
| (b-idx most-positive-fixnum)) |
| (dolist (e pylint--messages-list-hist) |
| (when (string= e a) |
| (setq a-idx idx)) |
| (when (string= e b) |
| (setq b-idx idx)) |
| (setq idx (1+ idx))) |
| (< a-idx b-idx))) |
| |
| (defun pylint--create-messages-list () |
| "Use `pylint-command' to populate `pylint--messages-list'." |
| ;; example output: |
| ;; |--we want this--| |
| ;; v v |
| ;; :using-cmp-argument (W1640): *Using the cmp argument for list.sort / sorted* |
| ;; Using the cmp argument for list.sort or the sorted builtin should be avoided, |
| ;; since it was removed in Python 3. Using either `key` or `functools.cmp_to_key` |
| ;; should be preferred. This message can't be emitted when using Python >= 3.0. |
| (setq pylint--messages-list |
| (split-string |
| (with-temp-buffer |
| (shell-command (concat pylint-command " --list-msgs") (current-buffer)) |
| (flush-lines "^[^:]") |
| (goto-char (point-min)) |
| (while (not (eobp)) |
| (delete-char 1) ;; delete ";" |
| (re-search-forward " ") |
| (delete-region (point) (line-end-position)) |
| (forward-line 1)) |
| (buffer-substring-no-properties (point-min) (point-max)))))) |
| |
| ;;;###autoload |
| (defun pylint-insert-ignore-comment (&optional arg) |
| "Insert a comment like \"# pylint: disable=msg1,msg2,...\". |
| |
| This command repeatedly uses `completing-read' to match known |
| messages, and ultimately inserts a comma-separated list of all |
| the selected messages. |
| |
| With prefix argument, only insert a comma-separated list (for |
| appending to an existing list)." |
| (interactive "*P") |
| (unless pylint--messages-list |
| (pylint--create-messages-list)) |
| (setq pylint--messages-list |
| (sort pylint--messages-list #'pylint--sort-messages)) |
| (let ((msgs ()) |
| (msg "") |
| (prefix (if arg |
| "," |
| "# pylint: disable=")) |
| (sentinel "[DONE]")) |
| (while (progn |
| (setq msg (completing-read |
| "Message: " |
| pylint--messages-list |
| nil t nil 'pylint--messages-list-hist sentinel)) |
| (unless (string= sentinel msg) |
| (add-to-list 'msgs msg 'append)))) |
| (setq pylint--messages-list-hist |
| (delete sentinel pylint--messages-list-hist)) |
| (insert prefix (mapconcat 'identity msgs ",")))) |
| |
| (define-compilation-mode pylint-mode "PYLINT" |
| (setq pylint-last-buffer (current-buffer)) |
| (set (make-local-variable 'compilation-error-regexp-alist) |
| pylint-regexp-alist) |
| (set (make-local-variable 'compilation-disable-input) t)) |
| |
| (defvar pylint-mode-map |
| (let ((map (make-sparse-keymap))) |
| (set-keymap-parent map compilation-minor-mode-map) |
| (define-key map " " 'scroll-up) |
| (define-key map "\^?" 'scroll-down) |
| (define-key map "\C-c\C-f" 'next-error-follow-minor-mode) |
| |
| (define-key map "\r" 'compile-goto-error) ;; ? |
| (define-key map "n" 'next-error-no-select) |
| (define-key map "p" 'previous-error-no-select) |
| (define-key map "{" 'compilation-previous-file) |
| (define-key map "}" 'compilation-next-file) |
| (define-key map "\t" 'compilation-next-error) |
| (define-key map [backtab] 'compilation-previous-error) |
| map) |
| "Keymap for PYLINT buffers. |
| `compilation-minor-mode-map' is a cdr of this.") |
| |
| (defun pylint--make-indent-string () |
| "Make a string for the `--indent-string' option." |
| (format "--indent-string='%s'" |
| (make-string python-indent-offset ?\ ))) |
| |
| ;;;###autoload |
| (defun pylint (&optional arg) |
| "Run PYLINT, and collect output in a buffer, much like `compile'. |
| |
| While pylint runs asynchronously, you can use \\[next-error] (M-x next-error), |
| or \\<pylint-mode-map>\\[compile-goto-error] in the grep \ |
| output buffer, to go to the lines where pylint found matches. |
| |
| \\{pylint-mode-map}" |
| (interactive "P") |
| |
| (save-some-buffers (not pylint-ask-about-save) nil) |
| (let* ((filename (buffer-file-name)) |
| (localname-offset (cl-struct-slot-offset 'tramp-file-name 'localname)) |
| (filename (or (and (tramp-tramp-file-p filename) |
| (elt (tramp-dissect-file-name filename) localname-offset)) |
| filename)) |
| (filename (shell-quote-argument filename)) |
| (pylint-command (if arg |
| pylint-alternate-pylint-command |
| pylint-command)) |
| (pylint-options (if (not pylint-use-python-indent-offset) |
| pylint-options |
| (append pylint-options |
| (list (pylint--make-indent-string))))) |
| (command (mapconcat |
| 'identity |
| (append `(,pylint-command) pylint-options `(,filename)) |
| " "))) |
| |
| (compilation-start command 'pylint-mode))) |
| |
| ;;;###autoload |
| (defun pylint-add-key-bindings () |
| (let ((map (cond |
| ((boundp 'py-mode-map) py-mode-map) |
| ((boundp 'python-mode-map) python-mode-map)))) |
| |
| ;; shortcuts in the tradition of python-mode and ropemacs |
| (define-key map (kbd "C-c m l") 'pylint) |
| (define-key map (kbd "C-c m p") 'previous-error) |
| (define-key map (kbd "C-c m n") 'next-error) |
| (define-key map (kbd "C-c m i") 'pylint-insert-ignore-comment) |
| nil)) |
| |
| ;;;###autoload |
| (defun pylint-add-menu-items () |
| (let ((map (cond |
| ((boundp 'py-mode-map) py-mode-map) |
| ((boundp 'python-mode-map) python-mode-map)))) |
| |
| (define-key map [menu-bar Python pylint-separator] |
| '("--" . pylint-separator)) |
| (define-key map [menu-bar Python next-error] |
| '("Next error" . next-error)) |
| (define-key map [menu-bar Python prev-error] |
| '("Previous error" . previous-error)) |
| (define-key map [menu-bar Python lint] |
| '("Pylint" . pylint)) |
| nil)) |
| |
| (provide 'pylint) |
| |
| ;;; pylint.el ends here |