Editor Integration¶
complexipy ships a Language Server Protocol (LSP) server, so your editor can
surface the same cognitive complexity analysis the CLI produces while you
write code. The server runs the same Rust engine as complexipy and the Python
API, and it needs no configuration to start being useful: install complexipy,
point your editor at complexipy lsp, and keep working.
Inside the editor you get:
- Inlay hints - the cognitive complexity of a function, shown at the end of
its
defline ascognitive: 18. - Hover - hovering the
deflines of a function shows its name, its cognitive complexity, whether it exceeds the allowed threshold, and the title of the top refactoring suggestion when one exists. - Diagnostics - one warning per function above
max-complexity-allowed, with the messagecognitive complexity 18 exceeds the allowed 15on the function name. - Code actions - every refactoring suggestion for a function above
max-complexity-allowed. A suggestion that is safe to apply is a quick fix that rewrites the buffer in one undoable edit. The others appear in the refactor menu with their help text.
Results refresh as you type. The server uses full document sync and analyzes only the documents you have open, so there is no workspace-wide scan and no need to save.
Installation¶
Install complexipy so that the complexipy command is on your PATH:
or, with pip:
Anything that installs the complexipy command works. The editor starts the
server as a subprocess with complexipy lsp; no separate binary download
exists. Upgrade the tool with uv tool upgrade complexipy or
pip install --upgrade complexipy to pick up new rules and settings.
Neovim¶
Neovim 0.11 and later can configure and enable the server with
vim.lsp.config and vim.lsp.enable:
vim.lsp.config("complexipy", {
cmd = { "complexipy", "lsp" },
filetypes = { "python" },
root_markers = { "complexipy.toml", ".complexipy.toml", "pyproject.toml", ".git" },
})
vim.lsp.enable("complexipy")
On Neovim 0.10, start the server directly instead:
Inlay hints are opt-in in Neovim. Enable them once, globally:
or per buffer with vim.lsp.inlay_hint.enable(true, { bufnr = 0 }).
Zed¶
Zed starts a language server only when Zed or an extension registers it. The
complexipy extension lives in the zed/ folder of the repository. Until it is
published in the Zed extension registry, install it as a dev extension:
- Clone
https://github.com/rohaquinlop/complexipy. - Run
zed: install dev extensionfrom the command palette and select thezed/folder.
Then enable the server and inlay hints for Python in settings.json:
{
"inlay_hints": { "enabled": true },
"languages": {
"Python": {
"language_servers": ["complexipy", "..."]
}
}
}
The "..." entry keeps every other Python language server you already use
enabled. If your settings already list Python language servers without
"...", add "complexipy" to that list, or Zed does not start it.
Zed turns inlay hints off by default. inlay_hints.enabled turns them on for
every language server, so type hints from servers such as ty or basedpyright
appear next to the complexipy hint.
The lsp.hover and lsp.diagnostic-range settings apply in Zed as well. The
colored badges of the VS Code extension do not: Zed shows the inlay hints.
The extension runs complexipy lsp from the PATH. To use another binary,
set its path:
{
"lsp": {
"complexipy": {
"binary": {
"path": "/full/path/to/complexipy",
"arguments": ["lsp"]
}
}
}
}
VS Code¶
Install the
complexipy extension
from the Visual Studio Marketplace, or from
OpenVSX in VSCodium and other forks.
It is a client of complexipy lsp: it starts the server for Python files and
shows its inlay hints, hover, and warnings. VS Code shows inlay hints by
default, so no editor setting is needed.
The extension also adds a status bar item with the number of functions over the threshold in the active file. Click it to open the Problems panel.
To see colored badges instead of inlay hints, set complexipy.display to
decorations. The extension then shows ƒ N after every function declaration
and +N after every line that adds complexity, in green, yellow, or red:
- A function is green up to half of
max-complexity-allowed, yellow up to the limit, and red above it. - A line is yellow from
complexipy.decorations.lineWarningAt(default3) and red fromcomplexipy.decorations.lineErrorAt(default5).
{
"complexipy.display": "decorations",
"complexipy.decorations.lineWarningAt": 3,
"complexipy.decorations.lineErrorAt": 5
}
The badges show every function and every line that adds complexity, whatever
inlay-hints and per-line-hints say. They need complexipy 8.2.0 or later.
With an older server the extension shows a warning and keeps the inlay hints.
The extension runs complexipy lsp from the PATH. To use another binary,
set its path in settings.json:
A relative path resolves against the first workspace folder. In Restricted
Mode, VS Code ignores a complexipy.server.path set in the workspace's
settings, so an untrusted repository cannot choose the binary; the extension
then runs the path from your user settings, or complexipy from the PATH.
Configuration¶
The server reads the same configuration files as the CLI - complexipy.toml,
.complexipy.toml, or pyproject.toml (under [tool.complexipy]) - discovered
at the root of each workspace folder:
[tool.complexipy]
max-complexity-allowed = 15
[tool.complexipy.lsp]
inlay-hints = "threshold" # "always" | "threshold" | "never"
per-line-hints = false
diagnostics = true
diagnostic-range = "name" # "name" | "function"
hover = "signature" # "signature" | "function" | "never"
workspace-diagnostics = false
| Key | Default | Description |
|---|---|---|
max-complexity-allowed |
15 |
Functions strictly above this value are reported. A function at the limit passes. |
exclude |
[] |
Glob patterns, matched relative to the workspace folder that holds the configuration file and to each of its paths entries. Excluded files produce no hints and no diagnostics. |
no-ignore |
false |
Analyze functions even when an inline ignore comment suppresses them. |
lsp.inlay-hints |
"threshold" |
When to show the per-function hint: "threshold", "always", or "never". |
lsp.per-line-hints |
false |
Also show a +N hint on every line with a non-zero complexity increment. |
lsp.diagnostics |
true |
Publish warnings for functions above max-complexity-allowed. |
lsp.diagnostic-range |
"name" |
What the warning underlines: "name" the function name, or "function" the whole function. |
lsp.hover |
"signature" |
Where hover answers: "signature" on the def lines, "function" on any line of the function, or "never". |
lsp.workspace-diagnostics |
false |
Also publish warnings for files that are not open. The server scans the workspace folder, or its paths entries, with the same file discovery as complexipy, and follows changes on disk when the editor watches files. |
The server ignores every other key of the same file: quiet,
failed, sort, color, output, output-format, cache-dir,
snapshot-create, snapshot-ignore, ignore-complexity, check-script,
report-ignored, force-exclude, and diff. Run those keys through complexipy <path>.
The server reads the configuration again when you save a change to one of
these files at the root of a workspace folder. The editor must support dynamic
registration of workspace/didChangeWatchedFiles. If a saved file does not
parse, the editor shows a warning and the folder uses the default settings.
With an editor that does not watch files, restart the server after you change
the configuration.
Inlay hints¶
The hint is placed at the end of the last line of the function declaration: the
line that closes the signature. For a single-line signature that is the def
line, and for a decorated function it is the def line rather than the
@decorator line above it. For a signature that spans several lines the hint
sits after the closing ) or )::
Here the hint sits at the end of the ): line.
inlay-hints = "threshold"(the default) shows the per-function hint (cognitive: 18) only when the function is abovemax-complexity-allowed.inlay-hints = "always"shows it for every function.inlay-hints = "never"suppresses every hint, including per-line hints.per-line-hints = trueadditionally shows a+Nhint at the end of every line that has a non-zero complexity increment. Lines with an increment of zero never produce a hint.
The server reports the same functions complexipy <path> reports: top-level
functions, plus methods of classes. A function nested inside another one is
folded into its parent, so there is no separate hint for it. With
hover = "function", hovering inside a nested function therefore shows its
parent.
Hover¶
hover = "signature"(the default) answers only on the declaration lines: from thedefline to the line that closes the signature. Hovering a call in the body leaves the editor's own hover alone.hover = "function"answers on any line of the function.hover = "never"turns hover off.
Diagnostics¶
diagnostics = falsepublishes no warnings at all.diagnostic-range = "name"(the default) underlines only the function name on itsdefline.diagnostic-range = "function"underlines the whole function.- The reported threshold matches
complexipy <path>: a function whose complexity equalsmax-complexity-allowedpasses. Only strictly greater values are reported, ascognitive complexity 18 exceeds the allowed 15. - A document the parser cannot read produces exactly one warning with the code
complexipy-parse-erroron the range where the parser fails, with the parser's message. While that warning is present, hints and hover keep answering from the last text that parsed, so the file does not go blank while you type. The warning clears itself as soon as the document parses again. - Each warning links to Understanding Scores, so the entry in the Problems panel opens the explanation of the score.
Quick fixes¶
Code actions answer for every function above max-complexity-allowed that the
requested range touches, with one action per refactoring suggestion, up to
five per function. The title names the rule and the reduction, as
C007: Merge nested if statements (-1). An estimated reduction reads
(~5, not measured).
- A suggestion that
complexipy --fixwould apply is a quick fix. Applying it sends one edit over the changed lines, so one undo reverts it. The first such suggestion of a function is the preferred fix. - Any other suggestion, such as a help-only rule like C003, is a disabled
refactor action, and its help text is the reason. It never edits the buffer.
Editors hide disabled actions from the automatic quick fix menu, so look for
it in the refactor menu: Refactor... (
Ctrl+Shift+R) in VS Code. - A document that does not parse offers no actions until it parses again.
Ignores and exclusions¶
excludeglobs are matched relative to the workspace folder that holds the configuration file, and relative to eachpathsentry of that file, the same way a CLI run in that folder matches them. Excluded files produce no hints and no diagnostics.- A nested workspace folder without a configuration file of its own uses the configuration of the nearest enclosing folder that has one.
- A pattern that can never match, such as
./vendor/**orvendor/, is reported as a warning. - A malformed glob is reported once per configuration load and skipped.
Each configuration problem (a malformed glob, an oversized list, an unknown
rule id in
selectorignore, a selection that leaves no rule active, or a file that does not parse) is written to the server log, and the editor shows one fixed warning that points to that log. The warning never quotes the configuration, so a repository cannot place a link in it. The other patterns in the list keep applying. - A list too large to compile as one program is reported once per configuration load and still applied one pattern at a time. A CLI run stops on the same list.
- Inline suppression comments (
# noqa: complexipyand# complexipy: ignore) are honored exactly as in the CLI, unlessno-ignore = true.
Troubleshooting¶
The editor cannot find the complexipy command¶
The editor starts the server as a subprocess, so complexipy must be on the
PATH that the editor process inherits. Point the server at an absolute path
when the tool is installed somewhere the editor cannot see:
- Neovim: set
cmd = { "/full/path/to/complexipy", "lsp" }. - Zed: set
"path"to the absolute binary in thelsp.complexipy.binaryblock. Without it, the extension looks forcomplexipyon thePATH, and Zed shows an error when it is not there. - VS Code: set
complexipy.server.pathto the absolute binary. Without it, the extension looks forcomplexipyon thePATH, and VS Code shows an error when it is not there.
Alternatively, start it through uv without installing anything globally:
Where to read the server logs¶
The server never writes anything except protocol frames to stdout; all logging goes to stderr. Read it where your editor collects it:
- Neovim:
:LspLog. - Zed: open the log panel from the command palette (
zed: open log). - VS Code: open the Output panel and select the
complexipychannel.
Hints do not appear¶
Inlay hints are opt-in in most editors. Enable them in the editor itself
(vim.lsp.inlay_hint.enable(true) in Neovim, "inlay_hints": { "enabled": true }
in Zed), and remember that the default
inlay-hints = "threshold" only shows the hint when a function is above
max-complexity-allowed. Set inlay-hints = "always" to see every function.
The whole file shows warnings¶
A limit lower than expected usually means the wrong configuration is active.
The server reads complexipy.toml, .complexipy.toml, or pyproject.toml
from the root of the document's workspace folder; check that the folder the
editor opened is the one holding your settings. The server reads the file at
startup, and again when you save it in an editor that watches files for the
server. In other editors, restart the server after you edit it (:LspRestart
in Neovim, editor: restart language server in Zed).
Per-line hints do not appear¶
per-line-hints is off by default. Set it to true to add the +N hints.
Limits¶
The --diff ratchet checks and the exit-code gates remain CLI-only. The
language server is meant to guide you while you write, not to replace the CI
contract, so existing CI usage is unchanged.
Other limits of the current server:
- The server analyzes Python documents only. A document whose language is not
pythonand whose path does not end in.pyproduces no hints and no warnings. - The server declares full document sync, so it expects the whole buffer on every change. A change that carries a range is ignored, and one line is written to the server log.
- In a workspace with several folders, each folder reads its own configuration file. A document uses the deepest folder that contains it, and a document outside every folder uses the first folder. Folders that the editor adds or removes take effect without a restart.
- The
initializationOptionsof the client are ignored. Settings come from the configuration file. - The
lspword reserves the first command-line argument:complexipy lspstarts the server. To analyze a file or directory namedlsp, runcomplexipy -- lsp. On a terminal the server prints that hint when a path namedlspexists.