diff options
| author | Jonas Bernoulli <jonas@bernoul.li> | 2021-03-29 19:08:00 +0200 |
|---|---|---|
| committer | brotzeit <brotzeitmacher@gmail.com> | 2021-04-23 13:57:07 +0200 |
| commit | 139a6580ed85d4ec1b29b23abeb474d33e78ea39 (patch) | |
| tree | 89bcb0b965187f1a09f7b66b8db281ca815254f9 | |
| parent | 41642f0573d51120b4ca46113c63977a55d68b27 (diff) | |
| download | rust-mode-139a6580ed85d4ec1b29b23abeb474d33e78ea39.tar.gz | |
Create rust-rustfmt.el from existing code
| -rw-r--r-- | Makefile | 1 | ||||
| -rw-r--r-- | rust-mode.el | 356 | ||||
| -rw-r--r-- | rust-rustfmt.el | 368 |
3 files changed, 369 insertions, 356 deletions
@@ -11,6 +11,7 @@ EMACS_ARGS ?= ELS = rust-mode.el ELS += rust-compile.el +ELS += rust-rustfmt.el ELCS = $(ELS:.el=.elc) DEPS = diff --git a/rust-mode.el b/rust-mode.el index f35a947..37b5cd3 100644 --- a/rust-mode.el +++ b/rust-mode.el @@ -70,34 +70,6 @@ When nil, `where' will be aligned with `fn' or `trait'." :safe #'booleanp :group 'rust-mode) -(defcustom rust-format-on-save nil - "Format future rust buffers before saving using rustfmt." - :type 'boolean - :safe #'booleanp - :group 'rust-mode) - -(defcustom rust-format-show-buffer t - "Show *rustfmt* buffer if formatting detected problems." - :type 'boolean - :safe #'booleanp - :group 'rust-mode) - -(defcustom rust-format-goto-problem t - "Jump to location of first detected problem when formatting buffer." - :type 'boolean - :safe #'booleanp - :group 'rust-mode) - -(defcustom rust-rustfmt-bin "rustfmt" - "Path to rustfmt executable." - :type 'string - :group 'rust-mode) - -(defcustom rust-rustfmt-switches '("--edition" "2018") - "Arguments to pass when invoking the `rustfmt' executable." - :type '(repeat string) - :group 'rust-mode) - (defcustom rust-cargo-bin "cargo" "Path to cargo executable." :type 'string @@ -1608,314 +1580,6 @@ This is written mainly to be used as `end-of-defun-function' for Rust." ;; There is no opening brace, so consider the whole buffer to be one "defun" (goto-char (point-max)))) -;;; Formatting using rustfmt - -(defconst rust-rustfmt-buffername "*rustfmt*") - -(defun rust--format-call (buf) - "Format BUF using rustfmt." - (with-current-buffer (get-buffer-create rust-rustfmt-buffername) - (view-mode +1) - (let ((inhibit-read-only t)) - (erase-buffer) - (insert-buffer-substring buf) - (let* ((tmpf (make-temp-file "rustfmt")) - (ret (apply 'call-process-region - (point-min) - (point-max) - rust-rustfmt-bin - t - `(t ,tmpf) - nil - rust-rustfmt-switches))) - (unwind-protect - (cond - ((zerop ret) - (if (not (string= (buffer-string) - (with-current-buffer buf (buffer-string)))) - ;; replace-buffer-contents was in emacs 26.1, but it - ;; was broken for non-ASCII strings, so we need 26.2. - (if (and (fboundp 'replace-buffer-contents) - (version<= "26.2" emacs-version)) - (with-current-buffer buf - (replace-buffer-contents rust-rustfmt-buffername)) - (copy-to-buffer buf (point-min) (point-max)))) - (kill-buffer)) - ((= ret 3) - (if (not (string= (buffer-string) - (with-current-buffer buf (buffer-string)))) - (copy-to-buffer buf (point-min) (point-max))) - (erase-buffer) - (insert-file-contents tmpf) - (rust--format-fix-rustfmt-buffer (buffer-name buf)) - (error "Rustfmt could not format some lines, see *rustfmt* buffer for details")) - (t - (erase-buffer) - (insert-file-contents tmpf) - (rust--format-fix-rustfmt-buffer (buffer-name buf)) - (error "Rustfmt failed, see *rustfmt* buffer for details")))) - (delete-file tmpf))))) - -;; Since we run rustfmt through stdin we get <stdin> markers in the -;; output. This replaces them with the buffer name instead. -(defun rust--format-fix-rustfmt-buffer (buffer-name) - (with-current-buffer (get-buffer rust-rustfmt-buffername) - (let ((inhibit-read-only t)) - (goto-char (point-min)) - (while (re-search-forward "--> <stdin>:" nil t) - (replace-match (format "--> %s:" buffer-name))) - (while (re-search-forward "--> stdin:" nil t) - (replace-match (format "--> %s:" buffer-name)))))) - -;; If rust-mode has been configured to navigate to source of the error -;; or display it, do so -- and return true. Otherwise return nil to -;; indicate nothing was done. -(defun rust--format-error-handler () - (let ((ok nil)) - (when rust-format-show-buffer - (display-buffer (get-buffer rust-rustfmt-buffername)) - (setq ok t)) - (when rust-format-goto-problem - (rust-goto-format-problem) - (setq ok t)) - ok)) - -(defun rust-goto-format-problem () - "Jumps to problem reported by rustfmt, if any. - -In case of multiple problems cycles through them. Displays the -rustfmt complain in the echo area." - (interactive) - ;; This uses position in *rustfmt* buffer to know which is the next - ;; error to jump to, and source: line in the buffer to figure which - ;; buffer it is from. - (let ((rustfmt (get-buffer rust-rustfmt-buffername))) - (if (not rustfmt) - (message "No *rustfmt*, no problems.") - (let ((target-buffer (with-current-buffer rustfmt - (save-excursion - (goto-char (point-min)) - (when (re-search-forward "--> \\([^:]+\\):" nil t) - (match-string 1))))) - (target-point (with-current-buffer rustfmt - ;; No save-excursion, this is how we cycle through! - (let ((regex "--> [^:]+:\\([0-9]+\\):\\([0-9]+\\)")) - (when (or (re-search-forward regex nil t) - (progn (goto-char (point-min)) - (re-search-forward regex nil t))) - (cons (string-to-number (match-string 1)) - (string-to-number (match-string 2))))))) - (target-problem (with-current-buffer rustfmt - (save-excursion - (when (re-search-backward "^error:.+\n" nil t) - (forward-char (length "error: ")) - (let ((p0 (point))) - (if (re-search-forward "\nerror:.+\n" nil t) - (buffer-substring p0 (point)) - (buffer-substring p0 (point-max))))))))) - (when (and target-buffer (get-buffer target-buffer) target-point) - (switch-to-buffer target-buffer) - (goto-char (point-min)) - (forward-line (1- (car target-point))) - (forward-char (1- (cdr target-point)))) - (message target-problem))))) - -(defconst rust--format-word "\ -\\b\\(else\\|enum\\|fn\\|for\\|if\\|let\\|loop\\|\ -match\\|struct\\|union\\|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-diff-buffer () - "Show diff to current buffer from rustfmt. - -Return the created process." - (interactive) - (unless (executable-find rust-rustfmt-bin) - (error "Could not locate executable \%s\"" rust-rustfmt-bin)) - (let* ((buffer - (with-current-buffer - (get-buffer-create "*rustfmt-diff*") - (let ((inhibit-read-only t)) - (erase-buffer)) - (current-buffer))) - (proc - (apply 'start-process - "rustfmt-diff" - buffer - rust-rustfmt-bin - "--check" - (cons (buffer-file-name) - rust-rustfmt-switches)))) - (set-process-sentinel proc 'rust-format-diff-buffer-sentinel) - proc)) - -(defun rust-format-diff-buffer-sentinel (process _e) - (when (eq 'exit (process-status process)) - (if (> (process-exit-status process) 0) - (with-current-buffer "*rustfmt-diff*" - (let ((inhibit-read-only t)) - (diff-mode)) - (pop-to-buffer (current-buffer))) - (message "rustfmt check passed.")))) - -(defun rust--format-buffer-using-replace-buffer-contents () - (condition-case err - (progn - (rust--format-call (current-buffer)) - (message "Formatted buffer with rustfmt.")) - (error - (or (rust--format-error-handler) - (signal (car err) (cdr err)))))) - -(defun rust--format-buffer-saving-position-manually () - (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 (frame (frame-list)) - (dolist (window (window-list frame)) - (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)))))) - (condition-case err - (unwind-protect - ;; save and restore window start position - ;; after reformatting - ;; to avoid the disturbing scrolling - (let ((w-start (window-start))) - (rust--format-call (current-buffer)) - (set-window-start (selected-window) w-start) - (message "Formatted buffer with rustfmt.")) - (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)))) - (unless (eq buffer current) - (set-window-start window start)) - (set-window-point window pos)))) - (error - (or (rust--format-error-handler) - (signal (car err) (cdr err))))))) - -(defun rust-format-buffer () - "Format the current buffer using rustfmt." - (interactive) - (unless (executable-find rust-rustfmt-bin) - (error "Could not locate executable \"%s\"" rust-rustfmt-bin)) - ;; If emacs version >= 26.2, we can use replace-buffer-contents to - ;; preserve location and markers in buffer, otherwise we can try to - ;; save locations as best we can, though we still lose markers. - (if (version<= "26.2" emacs-version) - (rust--format-buffer-using-replace-buffer-contents) - (rust--format-buffer-saving-position-manually))) - -(defun rust-enable-format-on-save () - "Enable formatting using rustfmt when saving buffer." - (interactive) - (setq-local rust-format-on-save t)) - -(defun rust-disable-format-on-save () - "Disable formatting using rustfmt when saving buffer." - (interactive) - (setq-local rust-format-on-save nil)) - (defun rust--compile (format-string &rest args) (when (null rust-buffer-project) (rust-update-buffer-project)) @@ -1955,26 +1619,6 @@ Return the created process." (interactive) (rust--compile "%s test" rust-cargo-bin)) -;;; Hooks - -(defun rust-before-save-hook () - (when rust-format-on-save - (condition-case e - (rust-format-buffer) - (error (format "rust-before-save-hook: %S %S" - (car e) - (cdr e)))))) - -(defun rust-after-save-hook () - (when rust-format-on-save - (if (not (executable-find rust-rustfmt-bin)) - (error "Could not locate executable \"%s\"" rust-rustfmt-bin) - (when (get-buffer rust-rustfmt-buffername) - ;; KLDUGE: re-run the error handlers -- otherwise message area - ;; would show "Wrote ..." instead of the error description. - (or (rust--format-error-handler) - (message "rustfmt detected problems, see *rustfmt* for more.")))))) - ;;; Secondary Commands (defun rust-playpen-region (begin end) diff --git a/rust-rustfmt.el b/rust-rustfmt.el new file mode 100644 index 0000000..d3b7509 --- /dev/null +++ b/rust-rustfmt.el @@ -0,0 +1,368 @@ +;;; rust-rustfmt.el --- Support for rustfmt -*- lexical-binding:t -*- +;;; Commentary: + +;; This library implements support for "rustfmt", a tool for +;; formatting Rust code according to style guidelines. + +;;; Code: +;;; Options + +(defcustom rust-format-on-save nil + "Format future rust buffers before saving using rustfmt." + :type 'boolean + :safe #'booleanp + :group 'rust-mode) + +(defcustom rust-format-show-buffer t + "Show *rustfmt* buffer if formatting detected problems." + :type 'boolean + :safe #'booleanp + :group 'rust-mode) + +(defcustom rust-format-goto-problem t + "Jump to location of first detected problem when formatting buffer." + :type 'boolean + :safe #'booleanp + :group 'rust-mode) + +(defcustom rust-rustfmt-bin "rustfmt" + "Path to rustfmt executable." + :type 'string + :group 'rust-mode) + +(defcustom rust-rustfmt-switches '("--edition" "2018") + "Arguments to pass when invoking the `rustfmt' executable." + :type '(repeat string) + :group 'rust-mode) + +;;; _ + +(defconst rust-rustfmt-buffername "*rustfmt*") + +(defun rust--format-call (buf) + "Format BUF using rustfmt." + (with-current-buffer (get-buffer-create rust-rustfmt-buffername) + (view-mode +1) + (let ((inhibit-read-only t)) + (erase-buffer) + (insert-buffer-substring buf) + (let* ((tmpf (make-temp-file "rustfmt")) + (ret (apply 'call-process-region + (point-min) + (point-max) + rust-rustfmt-bin + t + `(t ,tmpf) + nil + rust-rustfmt-switches))) + (unwind-protect + (cond + ((zerop ret) + (if (not (string= (buffer-string) + (with-current-buffer buf (buffer-string)))) + ;; replace-buffer-contents was in emacs 26.1, but it + ;; was broken for non-ASCII strings, so we need 26.2. + (if (and (fboundp 'replace-buffer-contents) + (version<= "26.2" emacs-version)) + (with-current-buffer buf + (replace-buffer-contents rust-rustfmt-buffername)) + (copy-to-buffer buf (point-min) (point-max)))) + (kill-buffer)) + ((= ret 3) + (if (not (string= (buffer-string) + (with-current-buffer buf (buffer-string)))) + (copy-to-buffer buf (point-min) (point-max))) + (erase-buffer) + (insert-file-contents tmpf) + (rust--format-fix-rustfmt-buffer (buffer-name buf)) + (error "Rustfmt could not format some lines, see *rustfmt* buffer for details")) + (t + (erase-buffer) + (insert-file-contents tmpf) + (rust--format-fix-rustfmt-buffer (buffer-name buf)) + (error "Rustfmt failed, see *rustfmt* buffer for details")))) + (delete-file tmpf))))) + +;; Since we run rustfmt through stdin we get <stdin> markers in the +;; output. This replaces them with the buffer name instead. +(defun rust--format-fix-rustfmt-buffer (buffer-name) + (with-current-buffer (get-buffer rust-rustfmt-buffername) + (let ((inhibit-read-only t)) + (goto-char (point-min)) + (while (re-search-forward "--> <stdin>:" nil t) + (replace-match (format "--> %s:" buffer-name))) + (while (re-search-forward "--> stdin:" nil t) + (replace-match (format "--> %s:" buffer-name)))))) + +;; If rust-mode has been configured to navigate to source of the error +;; or display it, do so -- and return true. Otherwise return nil to +;; indicate nothing was done. +(defun rust--format-error-handler () + (let ((ok nil)) + (when rust-format-show-buffer + (display-buffer (get-buffer rust-rustfmt-buffername)) + (setq ok t)) + (when rust-format-goto-problem + (rust-goto-format-problem) + (setq ok t)) + ok)) + +(defun rust-goto-format-problem () + "Jumps to problem reported by rustfmt, if any. + +In case of multiple problems cycles through them. Displays the +rustfmt complain in the echo area." + (interactive) + ;; This uses position in *rustfmt* buffer to know which is the next + ;; error to jump to, and source: line in the buffer to figure which + ;; buffer it is from. + (let ((rustfmt (get-buffer rust-rustfmt-buffername))) + (if (not rustfmt) + (message "No *rustfmt*, no problems.") + (let ((target-buffer (with-current-buffer rustfmt + (save-excursion + (goto-char (point-min)) + (when (re-search-forward "--> \\([^:]+\\):" nil t) + (match-string 1))))) + (target-point (with-current-buffer rustfmt + ;; No save-excursion, this is how we cycle through! + (let ((regex "--> [^:]+:\\([0-9]+\\):\\([0-9]+\\)")) + (when (or (re-search-forward regex nil t) + (progn (goto-char (point-min)) + (re-search-forward regex nil t))) + (cons (string-to-number (match-string 1)) + (string-to-number (match-string 2))))))) + (target-problem (with-current-buffer rustfmt + (save-excursion + (when (re-search-backward "^error:.+\n" nil t) + (forward-char (length "error: ")) + (let ((p0 (point))) + (if (re-search-forward "\nerror:.+\n" nil t) + (buffer-substring p0 (point)) + (buffer-substring p0 (point-max))))))))) + (when (and target-buffer (get-buffer target-buffer) target-point) + (switch-to-buffer target-buffer) + (goto-char (point-min)) + (forward-line (1- (car target-point))) + (forward-char (1- (cdr target-point)))) + (message target-problem))))) + +(defconst rust--format-word "\ +\\b\\(else\\|enum\\|fn\\|for\\|if\\|let\\|loop\\|\ +match\\|struct\\|union\\|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-diff-buffer () + "Show diff to current buffer from rustfmt. + +Return the created process." + (interactive) + (unless (executable-find rust-rustfmt-bin) + (error "Could not locate executable \%s\"" rust-rustfmt-bin)) + (let* ((buffer + (with-current-buffer + (get-buffer-create "*rustfmt-diff*") + (let ((inhibit-read-only t)) + (erase-buffer)) + (current-buffer))) + (proc + (apply 'start-process + "rustfmt-diff" + buffer + rust-rustfmt-bin + "--check" + (cons (buffer-file-name) + rust-rustfmt-switches)))) + (set-process-sentinel proc 'rust-format-diff-buffer-sentinel) + proc)) + +(defun rust-format-diff-buffer-sentinel (process _e) + (when (eq 'exit (process-status process)) + (if (> (process-exit-status process) 0) + (with-current-buffer "*rustfmt-diff*" + (let ((inhibit-read-only t)) + (diff-mode)) + (pop-to-buffer (current-buffer))) + (message "rustfmt check passed.")))) + +(defun rust--format-buffer-using-replace-buffer-contents () + (condition-case err + (progn + (rust--format-call (current-buffer)) + (message "Formatted buffer with rustfmt.")) + (error + (or (rust--format-error-handler) + (signal (car err) (cdr err)))))) + +(defun rust--format-buffer-saving-position-manually () + (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 (frame (frame-list)) + (dolist (window (window-list frame)) + (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)))))) + (condition-case err + (unwind-protect + ;; save and restore window start position + ;; after reformatting + ;; to avoid the disturbing scrolling + (let ((w-start (window-start))) + (rust--format-call (current-buffer)) + (set-window-start (selected-window) w-start) + (message "Formatted buffer with rustfmt.")) + (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)))) + (unless (eq buffer current) + (set-window-start window start)) + (set-window-point window pos)))) + (error + (or (rust--format-error-handler) + (signal (car err) (cdr err))))))) + +(defun rust-format-buffer () + "Format the current buffer using rustfmt." + (interactive) + (unless (executable-find rust-rustfmt-bin) + (error "Could not locate executable \"%s\"" rust-rustfmt-bin)) + ;; If emacs version >= 26.2, we can use replace-buffer-contents to + ;; preserve location and markers in buffer, otherwise we can try to + ;; save locations as best we can, though we still lose markers. + (if (version<= "26.2" emacs-version) + (rust--format-buffer-using-replace-buffer-contents) + (rust--format-buffer-saving-position-manually))) + +(defun rust-enable-format-on-save () + "Enable formatting using rustfmt when saving buffer." + (interactive) + (setq-local rust-format-on-save t)) + +(defun rust-disable-format-on-save () + "Disable formatting using rustfmt when saving buffer." + (interactive) + (setq-local rust-format-on-save nil)) + +;;; Hooks + +(defun rust-before-save-hook () + (when rust-format-on-save + (condition-case e + (rust-format-buffer) + (error (format "rust-before-save-hook: %S %S" + (car e) + (cdr e)))))) + +(defun rust-after-save-hook () + (when rust-format-on-save + (if (not (executable-find rust-rustfmt-bin)) + (error "Could not locate executable \"%s\"" rust-rustfmt-bin) + (when (get-buffer rust-rustfmt-buffername) + ;; KLDUGE: re-run the error handlers -- otherwise message area + ;; would show "Wrote ..." instead of the error description. + (or (rust--format-error-handler) + (message "rustfmt detected problems, see *rustfmt* for more.")))))) + +;;; _ +(provide 'rust-rustfmt) +;;; rust-rustfmt.el ends here |
