Init-Files/site-lisp/prettysharp.el
2019-03-21 21:07:20 +00:00

178 lines
6.5 KiB
EmacsLisp

;;; prettysharp.el --- Support for formatting C# with prettysharp
;;; Commentary:
;; Copyright 2019 John Doty. All rights reserved.
;; 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 of the License, 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. If not, see <https://www.gnu.org/licenses/>.
;; Author: John Doty
;; Version: 1.0.0
;; Keywords: convenience wp edit csharp
;; URL: https://github.com/decarabas/prettysharp
;;
;; This file is not part of GNU Emacs.
;;; Code:
(defgroup prettysharp nil
"Minor mode to format C# code on save."
:group 'languages
:prefix "prettysharp"
:link '(url-link "https://github.com/DeCarabas/PrettySharp"))
(defcustom prettysharp-command "prettysharp"
"The 'prettysharp' command."
:type 'string
:group 'prettysharp)
(defcustom prettysharp-show-errors 'echo
"Where to display prettysharp error output.
It can either be displayed in its own buffer, in the echo area,
or not at all."
:type '(choice
(const :tag "Own buffer" buffer)
(const :tag "Echo area" echo)
(const :tag "None" nil))
:group 'prettysharp)
(defconst prettysharp/error-buffer-name " *PrettySharp Errors*"
"The title of the buffer we show errors in.")
(defconst prettysharp/patch-buffer-name " *PrettySharp Patch*"
"The title of the buffer we format the patch in.")
(defun prettysharp/show-errors (outfile)
"Display the errors in OUTFILE.
Errors are displayed according to the value of prettysharp-show-errors."
(when prettysharp-show-errors
(let ((error-buffer (get-buffer-create prettysharp/error-buffer-name)))
(with-current-buffer error-buffer
(setq buffer-read-only nil)
(insert-file-contents outfile nil nil nil 'replace)
(cond
((eq prettysharp-show-errors 'buffer)
(goto-char (point-min))
(insert "prettysharp errors:\n")
(compilation-mode)
(display-buffer error-buffer))
((eq prettysharp-show-errors 'echo)
(message "%s" (buffer-string)))
)))))
(defun prettysharp/clear-errors ()
"Clear any errors from PrettySharp."
(let ((error-buffer (get-buffer prettysharp/error-buffer-name)))
(when error-buffer
(message "Clear errors?")
(with-current-buffer error-buffer
(setq buffer-read-only nil)
(erase-buffer))
(kill-buffer error-buffer))))
(defun prettysharp/make-patch (outfile patch-buffer)
"Diff the contents of the current buffer with OUTFILE, generate an RCS-style patch, and put the results into PATCH-BUFFER."
(call-process-region (point-min) (point-max) "diff" nil
patch-buffer nil "-n" "--strip-trailing-cr"
"-" outfile))
(defun prettysharp/apply-diff (patch-buffer)
"Apply the contents of PATCH-BUFFER as an RCS diff against the current buffer."
(let ((target-buffer (current-buffer))
(line-offset 0))
(save-excursion
(goto-char (point-min))
(with-current-buffer patch-buffer
(goto-char (point-min))
(while (not (eobp))
(cond
((looking-at "^$")
;; blank line is a valid command, meaning do nothing.
(forward-line))
((looking-at "^a\\([0-9]+\\) \\([0-9]+\\)$")
;; add lines.
(forward-line)
(let ((text-start (point))
(insert-at (string-to-number (match-string 1)))
(line-count (string-to-number (match-string 2))))
(forward-line line-count)
(let ((to-insert (buffer-substring text-start (point))))
(with-current-buffer target-buffer
(save-excursion
(goto-char (point-min))
(forward-line (+ insert-at line-offset))
;; It can happen that forward-line moves us to the end of
;; the buffer but not a blank line; in this case we need
;; to insert a newline.
(if (and (eobp) (looking-at "$") (not (looking-at "^")))
(insert "\n"))
(insert to-insert))
(setq line-offset (+ line-offset line-count))))))
((looking-at "^d\\([0-9]+\\) \\([0-9]+\\)$")
;; delete lines.
(forward-line)
(let ((delete-at (string-to-number (match-string 1)))
(line-count (string-to-number (match-string 2))))
(with-current-buffer target-buffer
(save-excursion
(goto-char (point-min))
(forward-line (1- (+ delete-at line-offset)))
(let ((delete-start (point)))
(forward-line line-count)
(delete-region delete-start (point))))
(setq line-offset (- line-offset line-count)))))
(t
(error "Unrecognized RCS command in prettysharp/apply-diff"))
))))))
(defun prettysharp ()
"Format the current buffer according to prettysharp."
(interactive)
(let ((outfile (make-temp-file "prettysharp" nil "cs"))
(patch-buffer (get-buffer-create prettysharp/patch-buffer-name))
(coding-system-for-read 'utf-8)
(coding-system-for-write 'utf-8))
(unwind-protect
(save-restriction
(widen)
(with-current-buffer patch-buffer
(erase-buffer))
(if (zerop (call-process-region (point-min) (point-max) prettysharp-command
nil (list :file outfile) nil))
(progn
(prettysharp/make-patch outfile patch-buffer)
(prettysharp/apply-diff patch-buffer)
(prettysharp/clear-errors))
(prettysharp/show-errors outfile)))
(delete-file outfile))))
;;;###autoload
(define-minor-mode prettysharp-mode
"Minor mode to run prettysharp on file save."
:lighter " pretty#"
(if prettysharp-mode
(add-hook 'before-save-hook 'prettysharp nil 'local)
(remove-hook 'before-save-hook 'prettysharp 'local)))
(provide 'prettysharp)
;;; prettysharp.el ends here