Skip to content

Instantly share code, notes, and snippets.

@fintelia
Created May 25, 2017 15:13
Show Gist options
  • Save fintelia/0353c29a39d11a6ea425c49baa469cba to your computer and use it in GitHub Desktop.
Save fintelia/0353c29a39d11a6ea425c49baa469cba to your computer and use it in GitHub Desktop.
Elisp script for applying rustfmt to a region of a buffer
(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