;;; tidyall.el -- Apply tidyall (https://metacpan.org/module/tidyall) to the current buffer

;; Copyright (C) 2012  Jonathan Swartz

;; Author: Jonathan Swartz <swartz@pobox.com>
;; Keywords: extensions
;; Status: Tested with Emacs 24.1.1

;; This file is *NOT* part of GNU Emacs.

;; 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 2, 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.


;; This package implements a single function, tidyall-buffer, which
;; runs tidyall (https://metacpan.org/module/tidyall) on the current buffer.

;; If successful, the contents of the buffer are replaced with the tidied contents, and
;; the buffer is saved if tidyall-autosave is true.  The modifications should be
;; undoable.

;; If tidyall generates any errors, the buffer is not changed, and a separate window
;; called *tidyall-output* is opened displaying the error.

;; To operate on just a region of the buffer, use narrow-to-region.

;; To assign this command to ctrl-t globally:
;;
;;   (global-set-key "\C-t" 'tidyall-buffer)
;;
;; Or to assign it locally in, e.g., perl-mode:
;;
;;   (setq perl-mode-hook
;;        '(lambda ()
;;           (local-set-key "\C-t" 'tidyall-buffer)
;;           ))
;;   
;; (This replaces the default binding to transpose-chars, which I never use but ymmv.)

;; The variable `tidyall-cmd` contains the path to the tidyall command.
;;
(setq tidyall-cmd "tidyall")

;; The variable `tidyall-autosave` indicates whether to save the buffer after a successful
;; tidy - defaults to t
;;
(setq tidyall-autosave t)

(defun tidyall-buffer ()
  "Run tidyall on the current buffer."
  (interactive)
  (let ((file (buffer-file-name)))
    (cond ((null file)
           (message "buffer has no filename"))
          (t
           (let* ((command (concat tidyall-cmd " -m editor --pipe " file))
                  (output-buffer (get-buffer-create "*tidyall-output*"))
                  (error-buffer (get-buffer-create "*tidyall-error*"))
                  (error-file (make-temp-file "tidyall_error"))
                  (start (point-min))
                  (end (point-max))
                  (orig-window-start (window-start (selected-window)))
                  (orig-point (point)))
             (with-current-buffer output-buffer (erase-buffer))
             (with-current-buffer error-buffer (erase-buffer))
             (let* ((result
                     (call-process-region
                      start end shell-file-name nil
                      (list output-buffer error-file) nil shell-command-switch command))
                    (output (with-current-buffer output-buffer (buffer-string))))
               (kill-buffer output-buffer)
               (cond ((zerop result)

                      ;; Success. Replace content if it changed
                      ;;
                      (cond ((not (equal output (buffer-string)))
                             (delete-region start end)
                             (insert output)

                             ;; Restore original window start and point as much as
                             ;; possible. Go to beginning of line since we'll probably be
                             ;; at a random point around our original line after the tidy.
                             ;;
                             (set-window-start (selected-window) orig-window-start)
                             (goto-char orig-point)
                             (beginning-of-line)
                             (message (concat "tidied " file)))
                            (t
                             (message (concat "checked " file))))
                      (when tidyall-autosave
                        (save-buffer))
                      (delete-windows-on error-buffer)
                      (kill-buffer error-buffer))
                        
                     (t
                      ;; Error. Display in other window
                      ;;
                      (with-current-buffer error-buffer
                        (insert-file-contents error-file))
                      (when (< (length (window-list)) 2)
                        (split-window-vertically))
                      (set-window-buffer (next-window) error-buffer))))
             (delete-file error-file))))))

(provide 'tidyall)
