emacs plugin to run jsfmt
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

134 lines
4.9 KiB

;; jsfmt.el -- Interface to jsfmt command for javascript files
;; https://rdio.github.io/jsfmt
;; Version 0.1.0
;; this is basically a copy of the necessary parts from
;; go-mode version 20131222, so all credit goes to
;; The Go Authors
(defcustom jsfmt-command "jsfmt"
"The 'jsfmt' command. https://rdio.github.io/jsfmt"
:type 'string
:group 'js)
(defun js--goto-line (line)
(goto-char (point-min))
(forward-line (1- line)))
(defalias 'js--kill-whole-line
(if (fboundp 'kill-whole-line)
#'kill-whole-line
#'kill-entire-line))
;; Delete the current line without putting it in the kill-ring.
(defun js--delete-whole-line (&optional arg)
;; Emacs uses both kill-region and kill-new, Xemacs only uses
;; kill-region. In both cases we turn them into operations that do
;; not modify the kill ring. This solution does depend on the
;; implementation of kill-line, but it's the only viable solution
;; that does not require to write kill-line from scratch.
(flet ((kill-region (beg end)
(delete-region beg end))
(kill-new (s) ()))
(js--kill-whole-line arg)))
(defun js--apply-rcs-patch (patch-buffer)
"Apply an RCS-formatted diff from PATCH-BUFFER to the current
buffer."
(let ((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 js--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
(decf line-offset len)
(goto-char (point-min))
(forward-line (- from len line-offset))
(insert text)))))
((equal action "d")
(with-current-buffer target-buffer
(js--goto-line (- from line-offset))
(incf line-offset len)
(js--delete-whole-line len)))
(t
(error "invalid rcs patch or internal error in js--apply-rcs-patch")))))))))
(defun jsfmt ()
"Formats the current buffer according to the jsfmt tool."
(interactive)
(let ((tmpfile (make-temp-file "jsfmt" nil ".js"))
(patchbuf (get-buffer-create "*Jsfmt patch*"))
(errbuf (get-buffer-create "*Jsfmt Errors*"))
(coding-system-for-read 'utf-8)
(coding-system-for-write 'utf-8))
(with-current-buffer errbuf
(setq buffer-read-only nil)
(erase-buffer))
(with-current-buffer patchbuf
(erase-buffer))
(write-region nil nil tmpfile)
;; We're using errbuf for the mixed stdout and stderr output. This
;; is not an issue because jsfmt -w does not produce any stdout
;; output in case of success.
(if (zerop (call-process jsfmt-command nil errbuf nil "-w" tmpfile))
(if (zerop (call-process-region (point-min) (point-max) "diff" nil patchbuf nil "-n" "-" tmpfile))
(progn
(kill-buffer errbuf)
(message "Buffer is already jsfmted"))
(js--apply-rcs-patch patchbuf)
(kill-buffer errbuf)
(message "Applied jsfmt"))
(message "Could not apply jsfmt. Check errors for details")
(jsfmt--process-errors (buffer-file-name) tmpfile errbuf))
(kill-buffer patchbuf)
(delete-file tmpfile)))
(defun jsfmt--process-errors (filename tmpfile errbuf)
;; Convert the jsfmt stderr to something understood by the compilation mode.
(with-current-buffer errbuf
(goto-char (point-min))
(insert "jsfmt errors:\n")
(while (search-forward-regexp (concat "^\\(" (regexp-quote tmpfile) "\\):") nil t)
(replace-match (file-name-nondirectory filename) t t nil 1))
(compilation-mode)
(display-buffer errbuf)))
(defun jsfmt-before-save ()
"Add this to .emacs to run jsfmt on the current buffer when saving:
(add-hook 'before-save-hook 'jsfmt-before-save).
Note that this will cause js-mode to get loaded the first time
you save any file, kind of defeating the point of autoloading."
(interactive)
(when (memq major-mode '(js-mode js2-mode js3-mode)) (jsfmt)))
(provide 'jsfmt)