#lsp #diagnostics #ide #protocols #command-line

app pickls

The General Purpose Language Server for Command-Line Linters and Formatters

30 releases (5 breaking)

new 0.6.15 Dec 8, 2024
0.6.14 Nov 28, 2024
0.5.5 Nov 15, 2024
0.4.2 Nov 1, 2024
0.1.1 Oct 26, 2024

#60 in Text editors

Download history 186/week @ 2024-10-21 835/week @ 2024-10-28 224/week @ 2024-11-04 535/week @ 2024-11-11 517/week @ 2024-11-18 1171/week @ 2024-11-25 188/week @ 2024-12-02

2,422 downloads per month

MIT license

110KB
2.5K SLoC

pickls

(pronounced /ˈpɪkᵊlz/)

The General Purpose Language Server for Command-Line Linters and Formatters

Inspired by tools like ale, null-ls, none-ls, and diagnostic-languageserver, and 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 to provide workspace symbol information. (See configuration notes below.)

Language Server Protocol - Primary Server Capabilities

  • diagnosticProvider (spec)
  • documentFormattingProvider (spec)
  • textDocumentSync (spec)
  • workspaceSymbolProvider (spec)
  • codeAction (spec)

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. Ensure you have a recent stable Rust toolchain and the cargo binary directory in your path:

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.

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

---
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:

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', '<leader>a', function() vim.lsp.buf.code_action() end)

Zed

To use pickls in Zed, install the pickls-zed extension. Use the following command:

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

{
  "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. 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.

Dependencies

~60MB
~1M SLoC