Moving all of my Emacs-related files into their own directory at the root of this repository.
		
			
				
	
	
		
			231 lines
		
	
	
	
		
			9.1 KiB
		
	
	
	
		
			EmacsLisp
		
	
	
	
	
	
			
		
		
	
	
			231 lines
		
	
	
	
		
			9.1 KiB
		
	
	
	
		
			EmacsLisp
		
	
	
	
	
	
| ;;; refmt.el --- utility functions to format reason code
 | |
| 
 | |
| ;; Copyright (c) 2014 The go-mode Authors. All rights reserved.
 | |
| ;; Portions Copyright (c) 2015-present, Facebook, Inc. All rights reserved.
 | |
| 
 | |
| ;; Redistribution and use in source and binary forms, with or without
 | |
| ;; modification, are permitted provided that the following conditions are
 | |
| ;; met:
 | |
| 
 | |
| ;; * Redistributions of source code must retain the above copyright
 | |
| ;; notice, this list of conditions and the following disclaimer.
 | |
| ;; * Redistributions in binary form must reproduce the above
 | |
| ;; copyright notice, this list of conditions and the following disclaimer
 | |
| ;; in the documentation and/or other materials provided with the
 | |
| ;; distribution.
 | |
| ;; * Neither the name of the copyright holder nor the names of its
 | |
| ;; contributors may be used to endorse or promote products derived from
 | |
| ;; this software without specific prior written permission.
 | |
| 
 | |
| ;; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 | |
| ;; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 | |
| ;; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 | |
| ;; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 | |
| ;; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 | |
| ;; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 | |
| ;; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 | |
| ;; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 | |
| ;; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 | |
| ;; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 | |
| ;; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.)
 | |
| 
 | |
| ;;; Commentary:
 | |
| ;;
 | |
| 
 | |
| ;;; Code:
 | |
| 
 | |
| (require 'cl-lib)
 | |
| 
 | |
| (defcustom refmt-command "refmt"
 | |
|   "The 'refmt' command."
 | |
|   :type 'string
 | |
|   :group 're-fmt)
 | |
| 
 | |
| (defcustom refmt-show-errors 'buffer
 | |
|     "Where to display refmt error output.
 | |
| It can either be displayed in its own buffer, in the echo area, or not at all.
 | |
| Please note that Emacs outputs to the echo area when writing
 | |
| files and will overwrite refmt's echo output if used from inside
 | |
| a `before-save-hook'."
 | |
|     :type '(choice
 | |
|             (const :tag "Own buffer" buffer)
 | |
|             (const :tag "Echo area" echo)
 | |
|             (const :tag "None" nil))
 | |
|       :group 're-fmt)
 | |
| 
 | |
| (defcustom refmt-width-mode nil
 | |
|   "Specify width when formatting buffer contents."
 | |
|   :type '(choice
 | |
|           (const :tag "Window width" window)
 | |
|           (const :tag "Fill column" fill)
 | |
|           (const :tag "None" nil))
 | |
|   :group 're-fmt)
 | |
| 
 | |
| ;;;###autoload
 | |
| (defun refmt-before-save ()
 | |
|   "Add this to .emacs to run refmt on the current buffer when saving:
 | |
|  (add-hook 'before-save-hook 'refmt-before-save)."
 | |
|     (interactive)
 | |
|       (when (eq major-mode 'reason-mode) (refmt)))
 | |
| 
 | |
| (defun reason--goto-line (line)
 | |
|   (goto-char (point-min))
 | |
|     (forward-line (1- line)))
 | |
| 
 | |
| (defun reason--delete-whole-line (&optional arg)
 | |
|     "Delete the current line without putting it in the `kill-ring'.
 | |
| Derived from function `kill-whole-line'.  ARG is defined as for that
 | |
| function."
 | |
|     (setq arg (or arg 1))
 | |
|     (if (and (> arg 0)
 | |
|              (eobp)
 | |
|              (save-excursion (forward-visible-line 0) (eobp)))
 | |
|         (signal 'end-of-buffer nil))
 | |
|     (if (and (< arg 0)
 | |
|              (bobp)
 | |
|              (save-excursion (end-of-visible-line) (bobp)))
 | |
|         (signal 'beginning-of-buffer nil))
 | |
|     (cond ((zerop arg)
 | |
|            (delete-region (progn (forward-visible-line 0) (point))
 | |
|                           (progn (end-of-visible-line) (point))))
 | |
|           ((< arg 0)
 | |
|            (delete-region (progn (end-of-visible-line) (point))
 | |
|                           (progn (forward-visible-line (1+ arg))
 | |
|                                  (unless (bobp)
 | |
|                                    (backward-char))
 | |
|                                  (point))))
 | |
|           (t
 | |
|            (delete-region (progn (forward-visible-line 0) (point))
 | |
|                                                   (progn (forward-visible-line arg) (point))))))
 | |
| 
 | |
| (defun reason--apply-rcs-patch (patch-buffer &optional start-pos)
 | |
|   "Apply an RCS-formatted diff from PATCH-BUFFER to the current buffer."
 | |
|   (setq start-pos (or start-pos (point-min)))
 | |
|   (let ((first-line (line-number-at-pos start-pos))
 | |
|         (target-buffer (current-buffer))
 | |
|         ;; Relative offset between buffer line numbers and line numbers
 | |
|         ;; in patch.
 | |
|         ;;
 | |
|         ;; Line numbers in the patch are based on the source file, so
 | |
|         ;; we have to keep an offset when making changes to the
 | |
|         ;; buffer.
 | |
|         ;;
 | |
|         ;; Appending lines decrements the offset (possibly making it
 | |
|         ;; negative), deleting lines increments it. This order
 | |
|         ;; simplifies the forward-line invocations.
 | |
|         (line-offset 0))
 | |
|     (save-excursion
 | |
|       (with-current-buffer patch-buffer
 | |
|         (goto-char (point-min))
 | |
|         (while (not (eobp))
 | |
|           (unless (looking-at "^\\([ad]\\)\\([0-9]+\\) \\([0-9]+\\)")
 | |
|             (error "invalid rcs patch or internal error in reason--apply-rcs-patch"))
 | |
|           (forward-line)
 | |
|           (let ((action (match-string 1))
 | |
|                 (from (string-to-number (match-string 2)))
 | |
|                 (len  (string-to-number (match-string 3))))
 | |
|             (cond
 | |
|              ((equal action "a")
 | |
|               (let ((start (point)))
 | |
|                 (forward-line len)
 | |
|                 (let ((text (buffer-substring start (point))))
 | |
|                   (with-current-buffer target-buffer
 | |
|                     (cl-decf line-offset len)
 | |
|                     (goto-char start-pos)
 | |
|                     (forward-line (- from len line-offset))
 | |
|                     (insert text)))))
 | |
|              ((equal action "d")
 | |
|               (with-current-buffer target-buffer
 | |
|                 (reason--goto-line (- (1- (+ first-line from)) line-offset))
 | |
|                 (cl-incf line-offset len)
 | |
|                 (reason--delete-whole-line len)))
 | |
|              (t
 | |
|               (error "invalid rcs patch or internal error in reason--apply-rcs-patch")))))))))
 | |
| 
 | |
| (defun refmt--process-errors (filename tmpfile errorfile errbuf)
 | |
|   (with-current-buffer errbuf
 | |
|     (if (eq refmt-show-errors 'echo)
 | |
|         (progn
 | |
|           (message "%s" (buffer-string))
 | |
|           (refmt--kill-error-buffer errbuf))
 | |
|       (insert-file-contents errorfile nil nil nil)
 | |
|       ;; Convert the refmt stderr to something understood by the compilation mode.
 | |
|       (goto-char (point-min))
 | |
|       (insert "refmt errors:\n")
 | |
|       (while (search-forward-regexp (regexp-quote tmpfile) nil t)
 | |
|         (replace-match (file-name-nondirectory filename)))
 | |
|       (compilation-mode)
 | |
|       (display-buffer errbuf))))
 | |
| 
 | |
| (defun refmt--kill-error-buffer (errbuf)
 | |
|   (let ((win (get-buffer-window errbuf)))
 | |
|     (if win
 | |
|         (quit-window t win)
 | |
|       (with-current-buffer errbuf
 | |
|         (erase-buffer))
 | |
|       (kill-buffer errbuf))))
 | |
| 
 | |
| (defun apply-refmt (&optional start end from to)
 | |
|   (setq start (or start (point-min))
 | |
|         end (or end (point-max))
 | |
|         from (or from "re")
 | |
|         to (or to "re"))
 | |
|    (let* ((ext (file-name-extension buffer-file-name t))
 | |
|           (bufferfile (make-temp-file "refmt" nil ext))
 | |
|           (outputfile (make-temp-file "refmt" nil ext))
 | |
|           (errorfile (make-temp-file "refmt" nil ext))
 | |
|           (errbuf (if refmt-show-errors (get-buffer-create "*Refmt Errors*")))
 | |
|           (patchbuf (get-buffer-create "*Refmt patch*"))
 | |
|           (coding-system-for-read 'utf-8)
 | |
|           (coding-system-for-write 'utf-8)
 | |
|           (width-args
 | |
|            (cond
 | |
|             ((equal refmt-width-mode 'window)
 | |
|              (list "--print-width" (number-to-string (window-body-width))))
 | |
|             ((equal refmt-width-mode 'fill)
 | |
|              (list "--print-width" (number-to-string fill-column)))
 | |
|             (t
 | |
|              '()))))
 | |
|      (unwind-protect
 | |
|          (save-restriction
 | |
|            (widen)
 | |
|            (write-region start end bufferfile)
 | |
|            (if errbuf
 | |
|                (with-current-buffer errbuf
 | |
|                  (setq buffer-read-only nil)
 | |
|                  (erase-buffer)))
 | |
|            (with-current-buffer patchbuf
 | |
|              (erase-buffer))
 | |
|            (if (zerop (apply 'call-process
 | |
|                              refmt-command nil (list (list :file outputfile) errorfile)
 | |
|                              nil (append width-args (list "--parse" from "--print" to bufferfile))))
 | |
|                (progn
 | |
|                  (call-process-region start end "diff" nil patchbuf nil "-n" "-"
 | |
|                                       outputfile)
 | |
|                  (reason--apply-rcs-patch patchbuf start)
 | |
|                  (message "Applied refmt")
 | |
|                  (if errbuf (refmt--kill-error-buffer errbuf)))
 | |
|              (message "Could not apply refmt")
 | |
|              (if errbuf
 | |
|                  (refmt--process-errors (buffer-file-name) bufferfile errorfile errbuf)))))
 | |
|      (kill-buffer patchbuf)
 | |
|      (delete-file errorfile)
 | |
|      (delete-file bufferfile)
 | |
|      (delete-file outputfile)))
 | |
| 
 | |
| (defun refmt ()
 | |
|   "Format the current buffer according to the refmt tool."
 | |
|   (interactive)
 | |
|   (apply-refmt))
 | |
| 
 | |
| (defun refmt-region-ocaml-to-reason (start end)
 | |
|   (interactive "r")
 | |
|   (apply-refmt start end "ml"))
 | |
| 
 | |
| (defun refmt-region-reason-to-ocaml (start end)
 | |
|   (interactive "r")
 | |
|   (apply-refmt start end "re" "ml"))
 | |
| 
 | |
| (provide 'refmt)
 | |
| 
 | |
| ;;; refmt.el ends here
 |