pythonemacsruff

Run Ruff in Emacs


How can I run Ruff in Emacs? I need to enable 2 commands on the current buffer:

I can run each of these commands with a file argument on the command line, and ruff is in my $PATH.

I tried the solutions from here: Setup | Ruff, and have the following lines in ~/.emacs, but they are not running ruff on save as expected. Besides, I really want to enable the two distinct commands above, rather than running ruff on save.

(add-hook 'python-mode-hook 'eglot-ensure)
(with-eval-after-load 'eglot
  (add-to-list 'eglot-server-programs
               '(python-mode . ("ruff" "server")))
  (add-hook 'after-save-hook 'eglot-format))

(require 'ruff-format)
(add-hook 'python-mode-hook 'ruff-format-on-save-mode)

Solution

  • I also tried out the proposal from Emacs to utilize the language server and failed. My reason was that I don't have ruff-format available. This can be checked by simply trying out M-xshell-command and then ruff-format. This can be easily fixed by installing it (e.g. via MELPA) or using a different command instead. But your requirements are different nevertheless, meaning that ruff should not be triggered automatically, but on certain user interactions.

    Therefore, I would simply recommend to not follow the setup instructions from Emacs page and define your own functions for both mentioned commands:

    For the M-xruff-check requirement you can define something like this:

    (defun ruff-check ()
      (interactive)
      (let ((current-file (buffer-file-name)))
        (if current-file
          (async-shell-command
            (format "ruff check --select ALL %s" (shell-quote-argument current-file))
          )
        )
      )
    )
    

    And for the M-xruff-fix requirement you can define something like this:

    (defun ruff-fix ()
      (interactive)
      (let ((current-file (buffer-file-name)))
        (if current-file
          (progn
            (shell-command
              (format "ruff check --select ALL --fix %s" (shell-quote-argument current-file))
            )
            (revert-buffer t t t)
          )
        )
      )
    )
    

    The interactive command can bind your command. In the example above, it is simply available as a command via M-x.

    This special form declares that a function is a command, and that it may therefore be called interactively (via M-x or by entering a key sequence bound to it).

    In the above function (ruff-check), I think we can use an async-command (minor improvement) since we are only reading. But in the ruff-fix it is easier to use a blocking command. Otherwise the buffer will not be refreshed correctly.

    I have a slightly different setup and using an .emacs.d folder (see here for the advantages) with an init.el inside, where I put the two functions above in. You can also create an extra file, e.g. ruff.el, but then you should not forget to load it in the init.el file: (load-file "~/.emacs.d/ruff.el)".

    Test setup:

    def foo() -> int:
        """docstring for foo."""
    
        return 42
    

    After M-xruff-check:

    Output after ruff-check

    After M-xruff-fix:

    Output after ruff-fix