;;; pylint.el --- minor mode for running `pylint' ;; Copyright (c) 2009, 2010 Ian Eure ;; Author: Ian Eure ;; Maintainer: Jonathan Kotta ;; 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 \\\\[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)) (filename (or (and (tramp-tramp-file-p filename) (aref (tramp-dissect-file-name filename) 3)) 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