# pickls
**(pronounced /ˈpɪkᵊlz/)**
## The General Purpose Language Server for Command-Line Linters and Formatters
Inspired by tools like [ale](https://github.com/dense-analysis/ale),
[null-ls](https://github.com/jose-elias-alvarez/null-ls.nvim),
[none-ls](https://github.com/nvimtools/none-ls.nvim), and
[diagnostic-languageserver](https://github.com/iamcco/diagnostic-languageserver),
and [conform](https://github.com/oberblastmeister/conform), `pickls` offers a
unified way to configure command-line linters, formatters and LLMs with editor
LSP integration.
### Key Features
- Integrate command-line linting and formatting tools with your IDE.
- Configure multiple linters and formatters for any language. This is ideal for
projects with toolchains lacking native LSP integration.
- Has a built-in code action for multi-LLM "Inline Assist" which can be used to
simultaneously query multiple LLMs for assistance. Currently OpenAI and Ollama
are supported.
- Supports dynamic invocation of [ctags](https://ctags.io/) to provide workspace
symbol information. (See configuration notes below.)
#### Language Server Protocol - Primary Server Capabilities
- `diagnosticProvider`
([spec](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#diagnosticOptions))
- `documentFormattingProvider`
([spec](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#documentFormattingOptions))
- `textDocumentSync`
([spec](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocumentSyncKindA))
- `workspaceSymbolProvider`
([spec](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspaceSymbolOptions))
- `codeAction`
([spec](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_codeAction))
## Why Use pickls?
- You'd like to have LSP support for LLM completion as a code action.
- Avoid installing and configuring separate plugins or language servers for each
tool in your workflow.
- Utilize a seamless LSP integration for command-line oriented toolchains.
## Installation
### Install pickls Binary
Pickls is available via crates.io [here](https://crates.io/crates/pickls).
Ensure you have a recent `stable` Rust toolchain and the cargo binary directory
in your path:
```sh
cargo install pickls
```
### Running from Source
Consider using `pickls-debug-runner` to run from source, which is helpful for
development purposes.
## Configuration
### User-level Configuration
Complete configuration source code is available
[here](https://docs.rs/crate/pickls/latest/source/src/config.rs).
User-level configuration lives in `"$XDG_CONFIG_HOME/pickls/pickls.yaml` where
`$XDG_CONFIG_HOME` defaults to `"$HOME"/.config`. Pickls will respect your
`$XDG_CONFIG_HOME` if it is set.
### Project-level Configuration
Project-level configuration is not yet implemented, but is on the roadmap.
### Example pickls.yaml
```yaml
---
ai:
inline_assistants:
- provider: ollama
model: tinyllama
- provider: ollama
model: llama3.2:latest
- provider: openai
model: gpt-4o
- provider: openai
model: gpt-4o-mini
symbols:
source: universal-ctags # Currently only universal-ctags is supported.
ctags_timeout_ms: 500 # 500ms is the default timeout.
languages:
c: &c-settings
formatters:
- program: clang-format
args: ["-"]
cpp: *c-settings
dockerfile:
linters:
- program: hadolint
args:
- --no-color
- --format
- tty
- '-'
description_match: 3
line_match: 1
pattern: '-:(\d+) [^ ]+ (\w+): (.*)'
severity_match: 2
use_stderr: false
use_stdin: true
lua:
linters:
- program: luacheck
args:
- --formatter
- plain
- --ranges
- --no-color
- '-'
pattern: 'stdin:(\d+):(\d+)-(\d+): (.*)'
line_match: 1
start_col_match: 2
end_col_match: 3
description_match: 4
use_stderr: false
use_stdin: true
formatters:
- program: lua-format
args:
- "--indent-width=2"
- "--spaces-inside-table-braces"
- "--align-table-field"
- "--break-before-table-rb"
- "--chop-down-table"
markdown:
formatters:
- program: mdformat
args:
- --wrap
- '80'
- '-'
python:
root_markers:
- .git
- pyproject.toml
- setup.py
- mypy.ini
formatters:
- program: autoimport
args: ["-"]
- program: isort
args: ["-", "-d"]
- program: ruff
args: ["check", "--exit-zero", "--fix", "--stdin-filename", "$filename"]
- program: ruff
args:
- format
- --stdin-filename
- $filename
linters:
# Try out [dmypyls](https://github.com/wbbradly/dmypyls).
- program: mypy
args:
- --show-column-numbers
- --show-error-end
- --hide-error-codes
- --hide-error-context
- --no-color-output
- --no-error-summary
- --no-pretty
- --shadow-file
- $filename
- /dev/stdin
- $filename
pattern: '(.*):(\d+):(\d+):\d+:(\d+): error: (.*)'
filename_match: 1
line_match: 2
start_col_match: 3
end_col_match: 4
description_match: 5
use_stderr: false
use_stdin: true
- program: ruff
args:
- check
- --stdin-filename
- $filename
pattern: '(.*):(\d+):(\d+): (.*)'
filename_match: 1
line_match: 2
start_col_match: 3
description_match: 4
use_stderr: false
use_stdin: true
sh: &sh
linters:
- program: shellcheck
args: ["-f", "gcc", "-"]
pattern: '(.*):(\d+):(\d+): (\w+): (.*)'
filename_match: 1
line_match: 2
start_col_match: 3
severity_match: 4
description_match: 5
use_stderr: false
use_stdin: true
bash: *sh
shell script: *sh
toml:
linters:
- program: tomllint
args: ["-"]
pattern: '(.*):(\d+):(\d+): error: (.*)'
filename_match: 1
line_match: 2
start_col_match: 3
description_match: 4
use_stderr: true
use_stdin: true
yaml:
linters:
- program: yamllint
args: ["-f", "parsable", "-"]
pattern: '.*:(\d+):(\d+): \[(.*)\] (.*) \((.*)\)'
line_match: 1
start_col_match: 2
severity_match: 3
description_match: 4
use_stderr: false
use_stdin: true
```
Note the usage of YAML anchors and references in order to handle different
language names for the same formats.
### Neovim
Enable `pickls` for all Neovim buffers:
```lua
vim.api.nvim_create_autocmd({ "BufRead" }, {
group = vim.api.nvim_create_augroup("pickls-bufread", { clear = true }),
callback = function(_)
if vim.fn.executable("pickls") ~= 0 then
vim.lsp.start({
name = "pickls",
cmd = { "pickls", vim.api.nvim_buf_get_name(0) },
root_dir = vim.fs.root(0, { ".git", "pyproject.toml", "setup.py", "Cargo.toml", "go.mod" }),
}, {
bufnr = 0,
reuse_client = function(_, _) return false end,
})
else
vim.notify("Pickls executable not found. See pickls-debug-runner for setup instructions.")
end
end,
})
-- Invoke LSP formatting on save.
vim.api.nvim_create_autocmd("BufWritePre", {
callback = function() vim.lsp.buf.format() end
})
-- You'll want to enable a shortcut for code actions in order to trigger inline-assist.
vim.keymap.set('n', 'a', function() vim.lsp.buf.code_action() end)
```
### Zed
To use `pickls` in Zed, install the
[pickls-zed](https://github.com/wbbradley/pickls-zed) extension. Use the
following command:
```bash
git clone https://github.com/wbbradley/pickls-zed "$HOME"/src/pickls-zed
```
Note that Zed supports formatting via command-line out of the box (see
`format_on_save`), so you don't really need to use `pickls` for formatting in
Zed. However, I've included it in the configuration here for demonstration
purposes.
#### Example Zed Settings
```jsonc
{
"format_on_save": "language_server",
"languages": {
"Python": {
// Note that this implicitly disables Zed's built-in usage of Pyright.
"language_servers": ["pickls"],
}
},
"lsp": {
"pickls": {
"binary": {"path": "pickls", "arguments": ["zed"]},
}
}
}
```
## Troubleshooting
If you encounter issues with `pickls`, please open an issue
[here](https://github.com/wbbradley/pickls/issues). When logging an issue,
please include the following information:
- How you have configured `pickls` in your editor.
- Any relevant lines from `"$HOME"/.local/state/pickls/pickls.log`.