Skip to content

Instantly share code, notes, and snippets.

@galeo
Last active December 16, 2015 16:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save galeo/5465488 to your computer and use it in GitHub Desktop.
Save galeo/5465488 to your computer and use it in GitHub Desktop.
Reindent python code in Emacs with the python reindent script by Tim Peters(https://pypi.python.org/pypi/Reindent/0.1.1).
;;; python-reindent.el --- Reindent Python Code
;;
;; Copyright (c) 2013 Moogen Tian
;;
;; Author: Moogen Tian <ibluefocus@NOSPAM.gmail.com>
;; Homepage: http://blog.galeo.me
;; url: https://gist.github.com/galeo/5465488
;; Version: 0.0.2
;; Created: Apr 25 2013
;; Keywords: python, reindent, format
;;
;;; This file is NOT part of GNU Emacs
;;
;;; License
;;
;; This program 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, or (at your option)
;; any later version.
;;
;; This program 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 this program; see the file COPYING. If not, write to
;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth
;; Floor, Boston, MA 02110-1301, USA.
;;
;;; Installation:
;;
;; Fist, install Python Reindent package (Which is released to
;; the public domain, by Tim Peters, 03 October 2000.) with pip:
;;
;; pip install -U Reindent
;;
;; Then, copy python-reindent.el to your load-path and add to your ~/.emacs
;;
;; (require 'python-reindent)
;;
;; Three commands are supplied to reindent python code:
;;
;; `python-reindent-region'
;; `python-reindent-file'
;; `python-reindent-directory'
;;
;; Run them interactively:
;;
;; M-x python-reindent-* RET
;;
;; Or set any key bindings you like.
;;
;;; Code:
(defun revert-buffer-keep-undo (&rest -)
"Revert buffer but keep undo history."
(interactive)
(let ((inhibit-read-only t))
(clear-visited-file-modtime)
(erase-buffer)
(insert-file-contents (buffer-file-name))
(set-visited-file-modtime)
(set-buffer-modified-p nil)))
(defun revert-python-buffers ()
"Refresh all opened buffers of python files."
(interactive)
(dolist (buf (buffer-list))
(with-current-buffer buf
(when (and (buffer-file-name)
(string-match "\\.\\(py\\|pyw\\)$"
(file-name-nondirectory (buffer-file-name)))
(not (buffer-modified-p)))
(revert-buffer-keep-undo t t t))))
(message "Refreshed opened python files."))
(defcustom python-reindent-command "reindent -v"
"Command used to reindent a python file.
Change Python (.py) files to use 4-space indents and no hard tab characters.
Also trim excess spaces and tabs from ends of lines, and remove empty lines
at the end of files. Also ensure the last line ends with a newline.
One or more file and/or directory paths can be passed as the arguments
of the command, reindent overwrites files in place. If backups are
required, it will rename the originals with a .bak extension.
If it finds nothing to change, the file is left alone.
If reindent does change a file, the changed file is a fixed-point for
future runs (i.e., running reindent on the resulting .py file won't
change it again)."
:type 'string
:group 'python)
(defun python-reindent-directory (dir &optional backup-p recurse-p)
"Search and reindent .py files in a directory.
If BACKUP-P is set to non-nil, backup files with .bak extension will
be generated.
All .py files within the directory will be examined, and, if RECURSE-P
is set to non-nil, subdirectories will be recursively searched.
Check `python-reindent-command' for what the indentation action will do."
(interactive
(let ((directory-name
(ido-read-directory-name "Reindent directory: "))
(recurse (y-or-n-p "Search recursively for all .py files?"))
(backup (y-or-n-p "Before reindentation, backup the files?")))
(list directory-name backup recurse)))
(save-some-buffers (not compilation-ask-about-save) nil)
(shell-command (concat python-reindent-command " "
(if (not backup-p)
"--nobackup ")
(if recurse-p
"--recurse ")
dir))
(revert-python-buffers)
(message
(concat "Reindent files done!"
(if backup-p
" Backup files with '.bak' extension generated."))))
(defun python-reindent-file (file &optional backup-p)
"Reindent a file(by default the one opened in current buffer).
If BACKUP-P is set to non-nil, a backup file with .bak extension will
be generated.
Check `python-reindent-command' for what the indentation action will do."
(interactive
(let* ((file-name
(ido-read-file-name
"Reindent file: " nil
(file-name-nondirectory (buffer-file-name))))
(backup (y-or-n-p "Before reindentation, backup the file?")))
(list file-name backup)))
(save-some-buffers (not compilation-ask-about-save) nil)
(shell-command (concat python-reindent-command " "
(if (not backup-p)
"--nobackup ")
file))
(revert-python-buffers)
(message
(concat "Reindent file done!"
(if backup-p
" A .bak file has been generated."))))
(defun python-reindent-region (beg end)
"Reindent the code of the region or the buffer if no region selected."
(interactive
(if (or (null transient-mark-mode)
mark-active)
(list (region-beginning) (region-end))
(list (point-min) (point-max))))
(let ((output (with-output-to-string
(shell-command-on-region
beg end
python-reindent-command
standard-output nil)))
(error-buffer "*Shell Output Error*"))
(if (and output
(not (string-match "^Traceback\\|^\\w+Error:" output)))
;; no error
(progn
(goto-char beg)
(kill-region beg end)
(insert output)
(message "Code has been reindented!"))
(progn
(set-buffer (get-buffer-create error-buffer))
(insert output)
(display-buffer error-buffer)
(message "Error occurred, please check!")))))
(provide 'python-reindent)
;;; python-reindent.el ends here
@galeo
Copy link
Author

galeo commented Jul 25, 2013

The function shell-command-on-region in emacs may not be well implemented, the discussion on StackOverFlow show the issues. When its argument REPLACE was set to non-nil and was called by the interactive command python-reindent-region, the selected region would always be replaced with the output the python-reindent-command generated, even the error info.

The output(code has been reindented) should be checked and the errors need to be properly processed.

Here is one worked version of python-reindent-region func:

(defun python-reindent-region (beg end)
  "Reindent the code of the region or the buffer if no region selected."
  (interactive
   (if (or (null transient-mark-mode)
           mark-active)
       (list (region-beginning) (region-end))
     (list (point-min) (point-max))))
  (let ((output (with-output-to-string
                  (shell-command-on-region
                   beg end
                   python-reindent-command
                   standard-output nil)))
        (error-buffer "*Shell Output Error*"))
    (if (and output
             (not (string-match "^Traceback\\|^\\w+Error:" output)))
        ;; no error
        (progn
          (goto-char beg)
          (kill-region beg end)
          (insert output)
          (message "Code has been reindented!"))
      (progn
        (set-buffer (get-buffer-create error-buffer))
        (insert output)
        (display-buffer error-buffer)
        (message "Error occurred, please check!")))))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment