ruff/docs/editors/setup.md
Dhruv Manilawala 1447553bc2
Improve logging system using logLevel, avoid trace value (#15232)
## Summary

Refer to the VS Code PR
(https://github.com/astral-sh/ruff-vscode/pull/659) for details on the
change.

This PR changes the following:

1. Add tracing span for both request (request id and method name) and
notification (method name) handler
2. Remove the `RUFF_TRACE` environment variable. This was being used to
turn on / off logging for the server
3. Similarly, remove reading the `trace` value from the initialization
options
4. Remove handling the `$/setTrace` notification
5. Remove the specialized `TraceLogWriter` used for Zed and VS Code
(https://github.com/astral-sh/ruff/pull/12564)

Regarding the (5) for the Zed editor, the reason that was implemented
was because there was no way of looking at the stderr messages in the
editor which has been changed. Now, it captures the stderr as part of
the "Server Logs".
(82492d74a8/crates/language_tools/src/lsp_log.rs (L548-L552))

### Question

Regarding (1), I think having just a simple trace level message should
be good for now as the spans are not hierarchical. This could be tackled
with #12744. The difference between the two:

<details><summary>Using <code>tracing::trace</code></summary>
<p>

```
   0.019243416s DEBUG ThreadId(08) ruff_server::session::index::ruff_settings: Ignored path via `exclude`: /Users/dhruv/playground/ruff/.vscode
   0.026398750s  INFO main ruff_server::session::index: Registering workspace: /Users/dhruv/playground/ruff
   0.026802125s TRACE ruff:main ruff_server::server::api: Received notification "textDocument/didOpen"
   0.026930666s TRACE ruff:main ruff_server::server::api: Received notification "textDocument/didOpen"
   0.026962333s TRACE ruff:main ruff_server::server::api: Received request "textDocument/diagnostic" (1)
   0.027042875s TRACE ruff:main ruff_server::server::api: Received request "textDocument/diagnostic" (2)
   0.027097500s TRACE ruff:main ruff_server::server::api: Received request "textDocument/codeAction" (3)
   0.027107458s DEBUG ruff:worker:0 ruff_server::resolve: Included path via `include`: /Users/dhruv/playground/ruff/lsp/play.py
   0.027123541s DEBUG ruff:worker:3 ruff_server::resolve: Included path via `include`: /Users/dhruv/playground/ruff/lsp/organize_imports.py
   0.027514875s  INFO     ruff:main ruff_server::server: Configuration file watcher successfully registered
   0.285689833s TRACE     ruff:main ruff_server::server::api: Received request "textDocument/codeAction" (4)
  45.741101666s TRACE     ruff:main ruff_server::server::api: Received notification "textDocument/didClose"
  47.108745500s TRACE     ruff:main ruff_server::server::api: Received notification "textDocument/didOpen"
  47.109802041s TRACE     ruff:main ruff_server::server::api: Received request "textDocument/diagnostic" (5)
  47.109926958s TRACE     ruff:main ruff_server::server::api: Received request "textDocument/codeAction" (6)
  47.110027791s DEBUG ruff:worker:6 ruff_server::resolve: Included path via `include`: /Users/dhruv/playground/ruff/lsp/play.py
  51.863679125s TRACE     ruff:main ruff_server::server::api: Received request "textDocument/hover" (7)
```

</p>
</details> 

<details><summary>Using <code>tracing::trace_span</code></summary>
<p>

Only logging the enter event:

```
   0.018638750s DEBUG ThreadId(11) ruff_server::session::index::ruff_settings: Ignored path via `exclude`: /Users/dhruv/playground/ruff/.vscode
   0.025895791s  INFO main ruff_server::session::index: Registering workspace: /Users/dhruv/playground/ruff
   0.026378791s TRACE ruff:main notification{method="textDocument/didOpen"}: ruff_server::server::api: enter
   0.026531208s TRACE ruff:main notification{method="textDocument/didOpen"}: ruff_server::server::api: enter
   0.026567583s TRACE ruff:main request{id=1 method="textDocument/diagnostic"}: ruff_server::server::api: enter
   0.026652541s TRACE ruff:main request{id=2 method="textDocument/diagnostic"}: ruff_server::server::api: enter
   0.026711041s DEBUG ruff:worker:2 ruff_server::resolve: Included path via `include`: /Users/dhruv/playground/ruff/lsp/organize_imports.py
   0.026729166s DEBUG ruff:worker:1 ruff_server::resolve: Included path via `include`: /Users/dhruv/playground/ruff/lsp/play.py
   0.027023083s  INFO     ruff:main ruff_server::server: Configuration file watcher successfully registered
   5.197554750s TRACE     ruff:main notification{method="textDocument/didClose"}: ruff_server::server::api: enter
   6.534458000s TRACE     ruff:main notification{method="textDocument/didOpen"}: ruff_server::server::api: enter
   6.535027958s TRACE     ruff:main request{id=3 method="textDocument/diagnostic"}: ruff_server::server::api: enter
   6.535271166s DEBUG ruff:worker:3 ruff_server::resolve: Included path via `include`: /Users/dhruv/playground/ruff/lsp/organize_imports.py
   6.544240583s TRACE     ruff:main request{id=4 method="textDocument/codeAction"}: ruff_server::server::api: enter
   7.049692458s TRACE     ruff:main request{id=5 method="textDocument/codeAction"}: ruff_server::server::api: enter
   7.508142541s TRACE     ruff:main request{id=6 method="textDocument/hover"}: ruff_server::server::api: enter
   7.872421958s TRACE     ruff:main request{id=7 method="textDocument/hover"}: ruff_server::server::api: enter
   8.024498583s TRACE     ruff:main request{id=8 method="textDocument/codeAction"}: ruff_server::server::api: enter
  13.895063666s TRACE     ruff:main request{id=9 method="textDocument/codeAction"}: ruff_server::server::api: enter
  14.774706083s TRACE     ruff:main request{id=10 method="textDocument/hover"}: ruff_server::server::api: enter
  16.058918958s TRACE     ruff:main notification{method="textDocument/didChange"}: ruff_server::server::api: enter
  16.060562208s TRACE     ruff:main request{id=11 method="textDocument/diagnostic"}: ruff_server::server::api: enter
  16.061109083s DEBUG ruff:worker:8 ruff_server::resolve: Included path via `include`: /Users/dhruv/playground/ruff/lsp/play.py
  21.561742875s TRACE     ruff:main notification{method="textDocument/didChange"}: ruff_server::server::api: enter
  21.563573791s TRACE     ruff:main request{id=12 method="textDocument/diagnostic"}: ruff_server::server::api: enter
  21.564206750s DEBUG ruff:worker:4 ruff_server::resolve: Included path via `include`: /Users/dhruv/playground/ruff/lsp/play.py
  21.826691375s TRACE     ruff:main request{id=13 method="textDocument/codeAction"}: ruff_server::server::api: enter
  22.091080125s TRACE     ruff:main request{id=14 method="textDocument/codeAction"}: ruff_server::server::api: enter
```

</p>
</details> 


**Todo**

- [x] Update documentation (I'll be adding a troubleshooting section
under "Editors" as a follow-up which is for all editors)
- [x] Check for backwards compatibility. I don't think this should break
backwards compatibility as it's mainly targeted towards improving the
debugging experience.

~**Before I go on to updating the documentation, I'd appreciate initial
review on the chosen approach.**~

resolves: #14959 

## Test Plan

Refer to the test plan in
https://github.com/astral-sh/ruff-vscode/pull/659.

Example logs at `debug` level:

```
   0.010770083s DEBUG ThreadId(15) ruff_server::session::index::ruff_settings: Ignored path via `exclude`: /Users/dhruv/playground/ruff/.vscode
   0.018101916s  INFO main ruff_server::session::index: Registering workspace: /Users/dhruv/playground/ruff
   0.018559916s DEBUG ruff:worker:4 ruff_server::resolve: Included path via `include`: /Users/dhruv/playground/ruff/lsp/play.py
   0.018992375s  INFO     ruff:main ruff_server::server: Configuration file watcher successfully registered
  23.408802375s DEBUG ruff:worker:11 ruff_server::resolve: Included path via `include`: /Users/dhruv/playground/ruff/lsp/play.py
  24.329127416s DEBUG  ruff:worker:6 ruff_server::resolve: Included path via `include`: /Users/dhruv/playground/ruff/lsp/play.py
```

Example logs at `trace` level:

```
   0.010296375s DEBUG ThreadId(13) ruff_server::session::index::ruff_settings: Ignored path via `exclude`: /Users/dhruv/playground/ruff/.vscode
   0.017422583s  INFO main ruff_server::session::index: Registering workspace: /Users/dhruv/playground/ruff
   0.018034458s TRACE ruff:main notification{method="textDocument/didOpen"}: ruff_server::server::api: enter
   0.018199708s TRACE ruff:worker:0 request{id=1 method="textDocument/diagnostic"}: ruff_server::server::api: enter
   0.018251167s DEBUG ruff:worker:0 request{id=1 method="textDocument/diagnostic"}: ruff_server::resolve: Included path via `include`: /Users/dhruv/playground/ruff/lsp/play.py
   0.018528708s  INFO     ruff:main ruff_server::server: Configuration file watcher successfully registered
   1.611798417s TRACE ruff:worker:1 request{id=2 method="textDocument/codeAction"}: ruff_server::server::api: enter
   1.861757542s TRACE ruff:worker:4 request{id=3 method="textDocument/codeAction"}: ruff_server::server::api: enter
   7.027361792s TRACE ruff:worker:2 request{id=4 method="textDocument/codeAction"}: ruff_server::server::api: enter
   7.851361500s TRACE ruff:worker:5 request{id=5 method="textDocument/codeAction"}: ruff_server::server::api: enter
   7.901690875s TRACE     ruff:main notification{method="textDocument/didChange"}: ruff_server::server::api: enter
   7.903063167s TRACE ruff:worker:10 request{id=6 method="textDocument/diagnostic"}: ruff_server::server::api: enter
   7.903183500s DEBUG ruff:worker:10 request{id=6 method="textDocument/diagnostic"}: ruff_server::resolve: Included path via `include`: /Users/dhruv/playground/ruff/lsp/play.py
   8.702385292s TRACE      ruff:main notification{method="textDocument/didChange"}: ruff_server::server::api: enter
   8.704106625s TRACE  ruff:worker:3 request{id=7 method="textDocument/diagnostic"}: ruff_server::server::api: enter
   8.704304875s DEBUG  ruff:worker:3 request{id=7 method="textDocument/diagnostic"}: ruff_server::resolve: Included path via `include`: /Users/dhruv/playground/ruff/lsp/play.py
   8.966853458s TRACE  ruff:worker:9 request{id=8 method="textDocument/codeAction"}: ruff_server::server::api: enter
   9.229622792s TRACE  ruff:worker:6 request{id=9 method="textDocument/codeAction"}: ruff_server::server::api: enter
  10.513111583s TRACE  ruff:worker:7 request{id=10 method="textDocument/codeAction"}: ruff_server::server::api: enter
```
2025-01-08 18:18:00 +05:30

16 KiB

Setup

We have specific setup instructions depending on your editor of choice. If you don't see your editor on this list and would like a setup guide, please open an issue.

If you're transferring your configuration from ruff-lsp, regardless of editor, there are several settings which have changed or are no longer available. See the migration guide for more.

!!! note

The setup instructions provided below are on a best-effort basis. If you encounter any issues
while setting up the Ruff in an editor, please [open an issue](https://github.com/astral-sh/ruff/issues/new)
for assistance and help in improving this documentation.

!!! tip

Regardless of the editor, it is recommended to disable the older language server
([`ruff-lsp`](https://github.com/astral-sh/ruff-lsp)) to prevent any conflicts.

VS Code

Install the Ruff extension from the VS Code Marketplace. It is recommended to have the Ruff extension version 2024.32.0 or later to get the best experience with the Ruff Language Server.

For more documentation on the Ruff extension, refer to the README of the extension repository.

Neovim

The nvim-lspconfig plugin can be used to configure the Ruff Language Server in Neovim. To set it up, install nvim-lspconfig plugin, set it up as per the configuration documentation, and add the following to your init.lua:

require('lspconfig').ruff.setup({
  init_options = {
    settings = {
      -- Ruff language server settings go here
    }
  }
})

!!! note

If the installed version of `nvim-lspconfig` includes the changes from
[neovim/nvim-lspconfig@`70d1c2c`](https://github.com/neovim/nvim-lspconfig/commit/70d1c2c31a88af4b36019dc1551be16bffb8f9db),
you will need to use Ruff version `0.5.3` or later.

If you're using Ruff alongside another language server (like Pyright), you may want to defer to that language server for certain capabilities, like textDocument/hover:

vim.api.nvim_create_autocmd("LspAttach", {
  group = vim.api.nvim_create_augroup('lsp_attach_disable_ruff_hover', { clear = true }),
  callback = function(args)
    local client = vim.lsp.get_client_by_id(args.data.client_id)
    if client == nil then
      return
    end
    if client.name == 'ruff' then
      -- Disable hover in favor of Pyright
      client.server_capabilities.hoverProvider = false
    end
  end,
  desc = 'LSP: Disable hover capability from Ruff',
})

If you'd like to use Ruff exclusively for linting, formatting, and organizing imports, you can disable those capabilities for Pyright:

require('lspconfig').pyright.setup {
  settings = {
    pyright = {
      -- Using Ruff's import organizer
      disableOrganizeImports = true,
    },
    python = {
      analysis = {
        -- Ignore all files for analysis to exclusively use Ruff for linting
        ignore = { '*' },
      },
    },
  },
}

By default, the log level for Ruff is set to info. To change the log level, you can set the logLevel setting:

require('lspconfig').ruff.setup {
  init_options = {
    settings = {
      logLevel = 'debug',
    }
  }
}

By default, Ruff will write logs to stderr which will be available in Neovim's LSP client log file (:lua vim.print(vim.lsp.get_log_path())). It's also possible to divert these logs to a separate file with the logFile setting.

To view the trace logs between Neovim and Ruff, set the log level for Neovim's LSP client to debug:

vim.lsp.set_log_level('debug')

Vim

The vim-lsp plugin can be used to configure the Ruff Language Server in Vim. To set it up, install vim-lsp plugin and register the server using the following in your .vimrc:

if executable('ruff')
    au User lsp_setup call lsp#register_server({
        \ 'name': 'ruff',
        \ 'cmd': {server_info->['ruff', 'server']},
        \ 'allowlist': ['python'],
        \ 'workspace_config': {},
        \ })
endif

See the vim-lsp documentation for more details on how to configure the language server.

If you're using Ruff alongside another LSP (like Pyright), you may want to defer to that LSP for certain capabilities, like textDocument/hover by adding the following to the function s:on_lsp_buffer_enabled():

function! s:on_lsp_buffer_enabled() abort
    " add your keybindings here (see https://github.com/prabirshrestha/vim-lsp?tab=readme-ov-file#registering-servers)

    let l:capabilities = lsp#get_server_capabilities('ruff')
    if !empty(l:capabilities)
      let l:capabilities.hoverProvider = v:false
    endif
endfunction

Ruff is also available as part of the coc-pyright extension for coc.nvim.

With the ALE plugin for Vim or Neovim.
" Linters
let g:ale_linters = { "python": ["ruff"] }
" Fixers
let g:ale_fixers = { "python": ["ruff", "ruff_format"] }

For the fixers, ruff will run ruff check --fix (to fix all auto-fixable problems) whereas ruff_format will run ruff format.

Ruff can also be integrated via efm language server in just a few lines.

Following is an example config for efm to use Ruff for linting and formatting Python files:

tools:
  python-ruff:
    lint-command: "ruff check --stdin-filename ${INPUT} --output-format concise --quiet -"
    lint-stdin: true
    lint-formats:
      - "%f:%l:%c: %m"
    format-command: "ruff format --stdin-filename ${INPUT} --quiet -"
    format-stdin: true
With the conform.nvim plugin for Neovim.
require("conform").setup({
    formatters_by_ft = {
        python = {
          -- To fix auto-fixable lint errors.
          "ruff_fix",
          -- To run the Ruff formatter.
          "ruff_format",
          -- To organize the imports.
          "ruff_organize_imports",
        },
    },
})
With the nvim-lint plugin for Neovim.
require("lint").linters_by_ft = {
  python = { "ruff" },
}

Helix

Open the language configuration file for Helix and add the language server as follows:

[language-server.ruff]
command = "ruff"
args = ["server"]

Then, you'll register the language server as the one to use with Python. If you don't already have a language server registered to use with Python, add this to languages.toml:

[[language]]
name = "python"
language-servers = ["ruff"]

Otherwise, if you already have language-servers defined, you can simply add "ruff" to the list. For example, if you already have pylsp as a language server, you can modify the language entry as follows:

[[language]]
name = "python"
language-servers = ["ruff", "pylsp"]

!!! note

Support for multiple language servers for a language is only available in Helix version
[`23.10`](https://github.com/helix-editor/helix/blob/master/CHANGELOG.md#2310-2023-10-24) and later.

If you want to, as an example, turn on auto-formatting, add auto-format = true:

[[language]]
name = "python"
language-servers = ["ruff", "pylsp"]
auto-format = true

See the Helix documentation for more settings you can use here.

You can pass settings into ruff server using [language-server.ruff.config.settings]. For example:

[language-server.ruff.config.settings]
lineLength = 80

[language-server.ruff.config.settings.lint]
select = ["E4", "E7"]
preview = false

[language-server.ruff.config.settings.format]
preview = true

By default, the log level for Ruff is set to info. To change the log level, you can set the logLevel setting:

[language-server.ruff]
command = "ruff"
args = ["server"]

[language-server.ruff.config.settings]
logLevel = "debug"

You can also divert Ruff's logs to a separate file with the logFile setting.

To view the trace logs between Helix and Ruff, pass in the -v (verbose) flag when starting Helix:

hx -v path/to/file.py

Kate

  1. Activate the LSP Client plugin.
  2. Setup LSP Client as desired.
  3. Finally, add this to Settings -> Configure Kate -> LSP Client -> User Server Settings:
{
  "servers": {
    "python": {
      "command": ["ruff", "server"],
      "url": "https://github.com/astral-sh/ruff",
      "highlightingModeRegex": "^Python$",
      "settings": {}
    }
  }
}

See LSP Client documentation for more details on how to configure the server from there.

!!! important

Kate's LSP Client plugin does not support multiple servers for the same language. As a
workaround, you can use the [`python-lsp-server`](https://github.com/python-lsp/python-lsp-server)
along with the [`python-lsp-ruff`](https://github.com/python-lsp/python-lsp-ruff) plugin to
use Ruff alongside another language server. Note that this setup won't use the [server settings](settings.md)
because the [`python-lsp-ruff`](https://github.com/python-lsp/python-lsp-ruff) plugin uses the
`ruff` executable and not the language server.

Sublime Text

To use Ruff with Sublime Text, install Sublime Text's LSP and LSP-ruff package.

PyCharm

Via External Tool

Ruff can be installed as an External Tool in PyCharm. Open the Preferences pane, then navigate to "Tools", then "External Tools". From there, add a new tool with the following configuration:

Install Ruff as an External Tool

Ruff should then appear as a runnable action:

Ruff as a runnable action

Via third-party plugin

Ruff is also available as the Ruff plugin on the IntelliJ Marketplace (maintained by @koxudaxi).

Emacs

Ruff can be utilized as a language server via Eglot, which is in Emacs's core. To enable Ruff with automatic formatting on save, use the following configuration:

(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))

Ruff is available as flymake-ruff on MELPA:

(require 'flymake-ruff)
(add-hook 'python-mode-hook #'flymake-ruff-load)

Ruff is also available as emacs-ruff-format:

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

Alternatively, it can be used via the Apheleia formatter library, by setting this configuration:

;; Replace default (black) to use ruff for sorting import and formatting.
(setf (alist-get 'python-mode apheleia-mode-alist)
      '(ruff-isort ruff))
(setf (alist-get 'python-ts-mode apheleia-mode-alist)
      '(ruff-isort ruff))

TextMate

Ruff is also available via the textmate2-ruff-linter bundle for TextMate.

Zed

Ruff is available as an extension for the Zed editor. To install it:

  1. Open the command palette with Cmd+Shift+P
  2. Search for "zed: extensions"
  3. Search for "ruff" in the extensions list and click "Install"

To configure Zed to use the Ruff language server for Python files, add the following to your settings.json file:

{
  "languages": {
    "Python": {
      "language_servers": ["ruff"]
      // Or, if there are other language servers you want to use with Python
      // "language_servers": ["pyright", "ruff"]
    }
  }
}

To configure the language server, you can provide the server settings under the lsp.ruff.initialization_options.settings key:

{
  "lsp": {
    "ruff": {
      "initialization_options": {
        "settings": {
          // Ruff server settings goes here
          "lineLength": 80,
          "lint": {
            "extendSelect": ["I"],
          }
        }
      }
    }
  }
}

!!! note

Support for multiple formatters for a given language is only available in Zed version
`0.146.0` and later.

You can configure Ruff to format Python code on-save by registering the Ruff formatter and enabling the format_on_save setting:

=== "Zed 0.146.0+"

```json
{
  "languages": {
    "Python": {
      "language_servers": ["ruff"],
      "format_on_save": "on",
      "formatter": [
        {
          "language_server": {
            "name": "ruff"
          }
        }
      ]
    }
  }
}
```

You can configure Ruff to fix lint violations and/or organize imports on-save by enabling the source.fixAll.ruff and source.organizeImports.ruff code actions respectively:

=== "Zed 0.146.0+"

```json
{
  "languages": {
    "Python": {
      "language_servers": ["ruff"],
      "format_on_save": "on",
      "formatter": [
        {
          "code_actions": {
            // Fix all auto-fixable lint violations
            "source.fixAll.ruff": true,
            // Organize imports
            "source.organizeImports.ruff": true
          }
        }
      ]
    }
  }
}
```

Taken together, you can configure Ruff to format, fix, and organize imports on-save via the following settings.json:

!!! note

For this configuration, it is important to use the correct order of the code action and
formatter language server settings. The code actions should be defined before the formatter to
ensure that the formatter takes care of any remaining style issues after the code actions have
been applied.

=== "Zed 0.146.0+"

```json
{
  "languages": {
    "Python": {
      "language_servers": ["ruff"],
      "format_on_save": "on",
      "formatter": [
        {
          "code_actions": {
            "source.organizeImports.ruff": true,
            "source.fixAll.ruff": true
          }
        },
        {
          "language_server": {
            "name": "ruff"
          }
        }
      ]
    }
  }
}
```