Created
May 25, 2017 15:13
-
-
Save fintelia/0353c29a39d11a6ea425c49baa469cba to your computer and use it in GitHub Desktop.
Elisp script for applying rustfmt to a region of a buffer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(defun rust--format-call-positions (buf start end) | |
"Format BUF using rustfmt." | |
(with-current-buffer (get-buffer-create "*rustfmt*") | |
(erase-buffer) | |
(insert-buffer-substring buf) | |
(if (zerop (call-process-region (point-min) (point-max) rust-rustfmt-bin t t nil "--file-lines" (format "[{\"file\":\"stdin\", \"range\":[%d,%d]}]" (line-number-at-pos start) (line-number-at-pos end)))) | |
(progn | |
(if (not (string= (buffer-string) (with-current-buffer buf (buffer-string)))) | |
(copy-to-buffer buf (point-min) (point-max))) | |
(kill-buffer)) | |
(error "Rustfmt failed, see *rustfmt* buffer for details")))) | |
(defconst rust--format-word "\\b\\(else\\|enum\\|fn\\|for\\|if\\|let\\|loop\\|match\\|struct\\|unsafe\\|while\\)\\b") | |
(defconst rust--format-line "\\([\n]\\)") | |
;; Counts number of matches of regex beginning up to max-beginning, | |
;; leaving the point at the beginning of the last match. | |
(defun rust--format-count (regex max-beginning) | |
(let ((count 0) | |
save-point | |
beginning) | |
(while (and (< (point) max-beginning) | |
(re-search-forward regex max-beginning t)) | |
(setq count (1+ count)) | |
(setq beginning (match-beginning 1))) | |
;; try one more in case max-beginning lies in the middle of a match | |
(setq save-point (point)) | |
(when (re-search-forward regex nil t) | |
(let ((try-beginning (match-beginning 1))) | |
(if (> try-beginning max-beginning) | |
(goto-char save-point) | |
(setq count (1+ count)) | |
(setq beginning try-beginning)))) | |
(when beginning (goto-char beginning)) | |
count)) | |
;; Gets list describing pos or (point). | |
;; The list contains: | |
;; 1. the number of matches of rust--format-word, | |
;; 2. the number of matches of rust--format-line after that, | |
;; 3. the number of columns after that. | |
(defun rust--format-get-loc (buffer &optional pos) | |
(with-current-buffer buffer | |
(save-excursion | |
(let ((pos (or pos (point))) | |
words lines columns) | |
(goto-char (point-min)) | |
(setq words (rust--format-count rust--format-word pos)) | |
(setq lines (rust--format-count rust--format-line pos)) | |
(if (> lines 0) | |
(if (= (point) pos) | |
(setq columns -1) | |
(forward-char 1) | |
(goto-char pos) | |
(setq columns (current-column))) | |
(let ((initial-column (current-column))) | |
(goto-char pos) | |
(setq columns (- (current-column) initial-column)))) | |
(list words lines columns))))) | |
;; Moves the point forward by count matches of regex up to max-pos, | |
;; and returns new max-pos making sure final position does not include another match. | |
(defun rust--format-forward (regex count max-pos) | |
(when (< (point) max-pos) | |
(let ((beginning (point))) | |
(while (> count 0) | |
(setq count (1- count)) | |
(re-search-forward regex nil t) | |
(setq beginning (match-beginning 1))) | |
(when (re-search-forward regex nil t) | |
(setq max-pos (min max-pos (match-beginning 1)))) | |
(goto-char beginning))) | |
max-pos) | |
;; Gets the position from a location list obtained using rust--format-get-loc. | |
(defun rust--format-get-pos (buffer loc) | |
(with-current-buffer buffer | |
(save-excursion | |
(goto-char (point-min)) | |
(let ((max-pos (point-max)) | |
(words (pop loc)) | |
(lines (pop loc)) | |
(columns (pop loc))) | |
(setq max-pos (rust--format-forward rust--format-word words max-pos)) | |
(setq max-pos (rust--format-forward rust--format-line lines max-pos)) | |
(when (> lines 0) (forward-char)) | |
(let ((initial-column (current-column)) | |
(save-point (point))) | |
(move-end-of-line nil) | |
(when (> (current-column) (+ initial-column columns)) | |
(goto-char save-point) | |
(forward-char columns))) | |
(min (point) max-pos))))) | |
(defun rust-format-region () | |
"Format the current buffer using rustfmt." | |
(interactive) | |
(unless (executable-find rust-rustfmt-bin) | |
(error "Could not locate executable \"%s\"" rust-rustfmt-bin)) | |
(let* ((current (current-buffer)) | |
(base (or (buffer-base-buffer current) current)) | |
buffer-loc | |
window-loc) | |
(dolist (buffer (buffer-list)) | |
(when (or (eq buffer base) | |
(eq (buffer-base-buffer buffer) base)) | |
(push (list buffer | |
(rust--format-get-loc buffer nil)) | |
buffer-loc))) | |
(dolist (window (window-list)) | |
(let ((buffer (window-buffer window))) | |
(when (or (eq buffer base) | |
(eq (buffer-base-buffer buffer) base)) | |
(let ((start (window-start window)) | |
(point (window-point window))) | |
(push (list window | |
(rust--format-get-loc buffer start) | |
(rust--format-get-loc buffer point)) | |
window-loc))))) | |
(if (use-region-p) | |
(progn (rust--format-call-positions (current-buffer) (region-beginning) (- (region-end) 1)) (deactivate-mark)) | |
(rust--format-call-positions (current-buffer) (point) (point))) | |
(dolist (loc buffer-loc) | |
(let* ((buffer (pop loc)) | |
(pos (rust--format-get-pos buffer (pop loc)))) | |
(with-current-buffer buffer | |
(goto-char pos)))) | |
(dolist (loc window-loc) | |
(let* ((window (pop loc)) | |
(buffer (window-buffer window)) | |
(start (rust--format-get-pos buffer (pop loc))) | |
(pos (rust--format-get-pos buffer (pop loc)))) | |
(set-window-start window start) | |
(set-window-point window pos))))) | |
(defun enable-rust-format-region () (local-set-key (kbd "<C-tab>") 'rust-format-region)) | |
(add-hook 'rust-mode-hook 'enable-rust-format-region) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment