-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathlazy-ruff.el
214 lines (181 loc) · 8.35 KB
/
lazy-ruff.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
;;; lazy-ruff.el --- Integration with the Ruff Python linter/formatter -*- lexical-binding: t; -*-
;; Copyright (C) 2024 Christopher Buch Madsen
;; Author: Christopher Buch Madsen
;; Version: 0.2.4
;; Package-Requires: ((emacs "24.3") (org "9.1"))
;; Keywords: languages, tools
;; URL: http://github.com/christophermadsen/emacs-lazy-ruff
;; This program is free software; you can redistribute it and/or modify it
;; under the terms of the GNU General Public License as published by the Free
;; Software Foundation, either version 3 of the License, or (at your option)
;; any later version.
;; This program is distributed in the hope that it will be useful, but WITHOUT
;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
;; FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
;; more details.
;; You should have received a copy of the GNU General Public License along with
;; this program. If not, see <https://www.gnu.org/licenses/>.
;; SPDX-License-Identifier: GPL-3.0-or-later
;;; Commentary:
;; This package provides Emacs commands to format and lint Python code using
;; the external 'ruff' tool. It offers functions to format the entire buffer,
;; specific regions, or Org mode source blocks.
;;
;; Prerequisites:
;; - The 'ruff' command-line tool must be installed and available in your
;; system's PATH. Please refer to the 'ruff' documentation at:
;; https://docs.astral.sh/ruff/installation/
;;
;; lazy-ruff quickstart with use-package:
;; (use-package lazy-ruff
;; :ensure t
;; :bind (("C-c f" . lazy-ruff-lint-format-dwim)) ;; keybinding
;; :config
;; (lazy-ruff-global-mode t)) ;; Enable the lazy-ruff minor mode globally
;;
;; For further information on how to use lazy-ruff, please refer to the README at:
;; https://github.com/christophermadsen/emacs-lazy-ruff
;;; Code:
(require 'org-element)
(require 'org)
(require 'python)
(defvar lazy-ruff-check-command "ruff check --fix -s"
"Defines the ruff check call for all methods.")
(defvar lazy-ruff-format-command "ruff format -s"
"Defines the ruff format call for all methods.")
(defvar lazy-ruff-only-format-block nil
"When non-nil (e.g. t), only format the code in a block without linting fixes.")
(defvar lazy-ruff-only-check-block nil
"When non-nil (e.g. t), only lint the code in a block without formatting fixes.")
(defvar lazy-ruff-only-format-buffer nil
"When non-nil (e.g. t), only format the code in a buffer without linting fixes.")
(defvar lazy-ruff-only-check-buffer nil
"When non-nil (e.g. t), only lint the code in a buffer without formatting fixes.")
(defvar lazy-ruff-only-format-region nil
"When non-nil (e.g. t), only format the code in a region without linting fixes.")
(defvar lazy-ruff-only-check-region nil
"When non-nil (e.g. t), only lint the code in a region without formatting fixes.")
(defvar lazy-ruff-org-src-languages '("python")
"Defines which languages in code blocks are eligible for formatting and linting")
(defun lazy-ruff-run-commands (temp-file only-format only-check)
"Run the appropriate ruff commands on TEMP-FILE.
If ONLY-FORMAT is true, only format the file.
If ONLY-CHECK is true, only check the file.
Otherwise, run both check and format commands."
(cond
(only-format
(shell-command-to-string (format "%s %s" lazy-ruff-format-command temp-file)))
(only-check
(shell-command-to-string (format "%s %s" lazy-ruff-check-command temp-file)))
(t
(progn
(shell-command-to-string (format "%s %s" lazy-ruff-check-command temp-file))
(shell-command-to-string (format "%s %s" lazy-ruff-format-command temp-file))))))
;;;###autoload
(defun lazy-ruff-lint-format-block ()
"Format Python `org-babel` blocks in Org mode using `ruff`.
Ensures cursor position is maintained. Requires `ruff` in system's PATH."
(interactive)
(let ((initial-line (line-number-at-pos))
(initial-column (current-column)))
(let* ((element (org-element-context))
(lang (org-element-property :language element))
(code (org-element-property :value element))
(temp-file (make-temp-file "emacs-org-ruff" nil ".py"))
formatted-code
(content-start (save-excursion
(goto-char (org-element-property :begin element))
(search-forward (concat "#+BEGIN_SRC " lang))
(line-end-position)))
(content-end (save-excursion
(goto-char (org-element-property :end element))
(search-backward "#+END_SRC")
(line-beginning-position))))
(if (not (member lang lazy-ruff-org-src-languages))
(message "The source block is not Python")
(with-temp-file temp-file (insert code))
(lazy-ruff-run-commands temp-file (eq lazy-ruff-only-format-block t) (eq lazy-ruff-only-check-block t))
(setq formatted-code (with-temp-buffer
(insert-file-contents temp-file)
(buffer-string)))
(delete-region (1+ content-start) content-end)
(goto-char (1+ content-start))
(insert formatted-code))
(delete-file temp-file))
(goto-char (point-min))
(forward-line (1- initial-line))
(move-to-column initial-column)))
;;;###autoload
(defun lazy-ruff-lint-format-buffer ()
"Format the current Python buffer using `ruff` before saving."
(interactive)
(unless (derived-mode-p 'python-mode 'python-base-mode)
(user-error "Only python buffers can be linted with ruff"))
(let ((temp-file (make-temp-file "ruff-tmp" nil ".py"))
(old-point (point))
(active-window (frame-selected-window))
(old-window-start (window-start))) ;;; Save point
;; Write buffer to temporary file, format it, and replace buffer contents.
(write-region nil nil temp-file)
(lazy-ruff-run-commands temp-file (eq lazy-ruff-only-format-buffer t) (eq lazy-ruff-only-check-buffer t))
(erase-buffer)
(insert-file-contents temp-file)
;; Clean up temporary file.
(delete-file temp-file)
;; Restore point and window
(goto-char old-point)
(set-window-start active-window old-window-start)))
;;;###autoload
(defun lazy-ruff-lint-format-region ()
"Format the currently selected region using `ruff`. Use at your own discretion."
(interactive)
(if (use-region-p)
(let* ((start (region-beginning))
(end (region-end))
(temp-file (make-temp-file "ruff-region-tmp" nil ".py"))
(temp-buffer (generate-new-buffer " *temp-ruff-output*")))
;; Write selected region to temporary file, format it.
(write-region start end temp-file nil 'silent)
(lazy-ruff-run-commands temp-file (eq lazy-ruff-only-format-region t) (eq lazy-ruff-only-check-region t))
;; Replace region with formatted content.
(with-current-buffer temp-buffer
(insert-file-contents temp-file))
(delete-region start end)
(insert-buffer-substring temp-buffer)
;; Cleanup actions.
(delete-file temp-file)
(kill-buffer temp-buffer))
(message "No region selected.")))
;;;###autoload
(defun lazy-ruff-lint-format-dwim ()
"Dispatch to the correct ruff format function based on the context."
(interactive)
(cond
;; First, check if a region is selected
((use-region-p)
(lazy-ruff-lint-format-region))
;; Next, check if inside an org-babel code block
((and (eq major-mode 'org-mode)
(org-in-src-block-p))
(lazy-ruff-lint-format-block))
;; Lastly, check if the current buffer is a Python mode buffer
((derived-mode-p 'python-mode 'python-base-mode)
(lazy-ruff-lint-format-buffer))
(t
(message "Not in a Python buffer or org-babel block, and no region is selected."))))
;;;###autoload
(define-minor-mode lazy-ruff-mode
"Toggle automatic formatting with Ruff in a Python buffer."
:lighter " Lazy-Ruff"
:global nil
(if lazy-ruff-mode
(add-hook 'before-save-hook #'lazy-ruff-lint-format-buffer nil t)
(remove-hook 'before-save-hook #'lazy-ruff-lint-format-buffer t)))
;;;###autoload
(define-globalized-minor-mode lazy-ruff-global-mode lazy-ruff-mode
(lambda () (when (derived-mode-p 'python-mode 'python-base-mode)
(lazy-ruff-mode 1))))
;;;###autoload
(define-obsolete-function-alias 'lazy-ruff-mode-global-toggle 'lazy-ruff-global-mode "0.2.2")
(provide 'lazy-ruff)
;;; lazy-ruff.el ends here