MCPcopy Index your code
hub / github.com/crisidev/bacon-ls

github.com/crisidev/bacon-ls @0.29.0

Chat with this repo
repository ↗ · DeepWiki ↗ · release 0.29.0 ↗ · + Follow
252 symbols 486 edges 10 files 24 documented · 10%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

🐽 Bacon Language Server 🐽

Ci Release Crates.io Crates.io License Codecov

Are you tired of rust-analyzer diagnostics being slow?

LSP Server wrapper for the exceptional Bacon exposing textDocument/diagnostic and workspace/diagnostic capabilities.

bacon-ls 🐽 does not substitute rust-analyzer, it's a companion tool that can help with large codebases where rust-analyzer can become slow dealing with diagnostics.

bacon-ls 🐽 does not help with completion, analysis, refactor, etc... For these, rust-analyzer must be running.

Bacon screenshot

See bacon-ls 🐽 blog post: https://lmno.lol/crisidev/bacon-language-server

bacon-ls 🐽 is meant to be easy to include in your IDE configuration.

Bacon gif

Features

  • Two backends to produce diagnostics:
  • Cargo (default since 0.23.0): runs cargo check (or cargo clippy) directly with JSON output, parses the messages and publishes them. Faster, lighter and zero extra dependencies.
  • Bacon: reads the export file produced by Bacon and publishes those diagnostics. Useful when you already have bacon running.
  • Push diagnostics to the LSP client on file save, open, close and rename.
  • Precise diagnostic positions and macro-expanded spans pointed back at the call-site.
  • Replacement code actions as suggested by cargo / clippy.
  • Unused / dead / deprecated code tagged with the LSP UNNECESSARY and DEPRECATED diagnostic tags (cargo backend only) so editors render unused variables and imports faded, and deprecated items struck through.
  • Streaming partial publishes during a long cargo run (configurable refresh interval) so the editor lights up as soon as the first errors are known.
  • Manual bacon_ls.run LSP command to re-trigger a check on demand.
  • Bacon backend extras: automatic validation of bacon preferences, optional creation of the preferences file, optional automatic background bacon process (requires bacon 3.8.0), open-file diagnostic synchronization.
  • Support for cargo workspaces.

Limitations

  • Windows support is not tested and probably broken - #10

Installation

VSCode

First, install Bacon.

The VSCode extension is available on both VSCE and OVSX:

Mason.nvim

Both Bacon and Bacon-ls are installable via mason.nvim:

:MasonInstall bacon bacon-ls

Manual

First, install Bacon and bacon-ls 🐽

❯❯❯ cargo install --locked bacon bacon-ls
❯❯❯ bacon --version
bacon 3.8.0  # make sure you have at least 3.8.0
❯❯❯ bacon-ls --version
0.14.0        # make sure you have at least 0.14.0

Nix

Both bacon and bacon-ls can be consumed from their Nix flakes.

Configuration

bacon-ls 🐽 reads its configuration from the bacon_ls section of the LSP client settings. All fields are optional — if you provide nothing the cargo backend starts with sensible defaults. The complete schema is:

{
  "bacon_ls": {
    // "cargo" or "bacon". Optional — see "Choosing a backend" below.
    "backend": "cargo",

    "cargo": {
      "command": "check",                 // "check" or "clippy"
      "features": [],                     // cargo --features list
      "package": null,                    // cargo -p <package>
      "extraArgs": [],                    // appended verbatim after the cargo command
      "env": {},                          // extra environment variables (string -> string)
      "cancelRunning": true,              // cancel an in-flight run when a new one is triggered
      "refreshIntervalSeconds": 1,        // partial publish interval; null/negative = wait until done
      "separateChildDiagnostics": null,   // override "related information" support; null = follow client
      "checkOnSave": true,                // trigger cargo on textDocument/didSave
      "clearDiagnosticsOnCheck": false,   // clear existing diagnostics before each run
      "updateOnInsertDebounceMillis": 500 // debounce for live diagnostics; updateOnInsert itself is in init_options
    },

    "bacon": {
      "locationsFile": ".bacon-locations",
      "runInBackground": true,
      "runInBackgroundCommand": "bacon",
      "runInBackgroundCommandArguments": "--headless -j bacon-ls",
      "validatePreferences": true,
      "createPreferencesFile": true,
      "synchronizeAllOpenFilesWaitMillis": 2000,
      "updateOnSave": true,
      "updateOnSaveWaitMillis": 1000
    }
  }
}

Choosing a backend

The backend is chosen once, when the server initializes, and cannot be switched at runtime (you have to restart the server). The choice is resolved as follows:

  1. If bacon_ls.backend is set to "cargo" or "bacon", that wins.
  2. Otherwise, if only one of bacon_ls.cargo or bacon_ls.bacon is present in the settings, that backend is selected.
  3. Otherwise (both sections present without an explicit backend, or no settings at all), the default is cargo.

Providing both cargo and bacon sections without an explicit backend key is reported as a configuration error.

Cargo backend options

Available since bacon-ls 0.23.0, default since 0.26.0. Runs cargo directly with --message-format=json-diagnostic-rendered-ansi, parses the stream and publishes diagnostics — no bacon process required.

  • command (default "check"): which cargo subcommand to run. Most useful values are "check" and "clippy".
  • features: list of features passed as --features a,b,c.
  • package: when set, passed as -p <package> (useful in workspaces).
  • extraArgs: appended verbatim after the subcommand. Use this for e.g. ["--workspace", "--all-targets", "--all-features"].
  • env: map of additional environment variables for the cargo invocation.
  • cancelRunning (default true): when a new run is requested while another is still running, cancel the in-flight one. Set to false to instead queue at most one follow-up run after the current one completes.
  • refreshIntervalSeconds (default 1): how often to publish a partial snapshot of the diagnostics gathered so far while cargo is still running. The very first diagnostic of a run is always published immediately so the editor lights up as soon as cargo emits something; this interval governs the cadence of refreshes after that. Set to null or a negative number to only publish once cargo has finished.
  • separateChildDiagnostics (default null): cargo emits some hints as children of a parent diagnostic. When null we follow the client's relatedInformation capability; set to true to always emit children as standalone diagnostics, false to always nest them.
  • checkOnSave (default true): trigger a cargo run on textDocument/didSave. Set to false if you only want to drive runs manually via bacon_ls.run.
  • clearDiagnosticsOnCheck (default false): publish empty diagnostics for all files that previously had any before starting the new run. Useful if you want the editor's diagnostic counters to drop to zero immediately at the start of a check.
  • updateOnInsertDebounceMillis (default 500): when live diagnostics are on (see below), how long the server waits after the last keystroke before triggering a cargo run against the shadow workspace. Lower values feel snappier; higher values reduce the number of cargo invocations during a burst of edits.

Live diagnostics as you type (cargo backend only)

The cargo backend can publish diagnostics on every keystroke instead of waiting for a save. This is opt-in and turned off by default: when it's off, the server doesn't even ask the editor for change events.

How it works: on the first dirty buffer, bacon-ls builds a "shadow" workspace at target/bacon-ls-live/shadow/ by hardlinking every .gitignore-respected file from the real workspace. Subsequent keystrokes write only the dirty buffer's bytes into the shadow (breaking the hardlink so the real file stays untouched), and a debounced cargo run targets the shadow with --target-dir=target/bacon-ls-live/target and --remap-path-prefix=<shadow>=<real> so diagnostics open the user's source file rather than a target/ copy. On didSave / didClose the file's shadow entry is replaced with a fresh hardlink to disk.

To enable it the flag has to come through initialization_options, not workspace settings. The reason is timing: the LSP textDocument/didChange sync capability has to be advertised statically before workspace configuration arrives, and clients (Neovim in particular) don't reliably retrofit already-attached buffers when the server tries to register that capability dynamically after initialized.

For Neovim's vim.lsp.config:

vim.lsp.config('bacon-ls', {
    init_options = {
        cargo = { updateOnInsert = true },
    },
    settings = {
        bacon_ls = {
            backend = "cargo",
            cargo = {
                command = "clippy",
                -- updateOnInsert lives in init_options above; only the
                -- runtime knob lives here:
                updateOnInsertDebounceMillis = 500,
            },
        },
    },
})

For LazyVim:

bacon_ls = {
    enabled = true,
    init_options = {
        cargo = { updateOnInsert = true },
    },
    settings = {
        bacon_ls = {
            backend = "cargo",
            cargo = {
                command = "clippy",
                updateOnInsertDebounceMillis = 500,
            },
        },
    },
},

Tradeoffs and caveats:

  • Linux-first. Hardlinking and --remap-path-prefix work cross-platform, but the integration tests cover Linux only. Mileage on macOS/Windows may vary.
  • Separate target directory. The shadow run uses its own target/bacon-ls-live/target/ so the live cargo invocation doesn't invalidate caches for the real cargo build you might run in a terminal. Cost: extra disk space (typically the size of one debug build).
  • First run is cold. Building the shadow workspace and the separate-target cargo cache is a one-shot cost that can take a few seconds on a large project. Subsequent runs are incremental.
  • No filesystem watcher. Files added or deleted on disk while the editor is open won't be reflected in the shadow until the next time the server rebuilds it (currently: an LSP restart). Touching a file you've already opened works, because we mirror its dirty state through didChange.
  • .gitignore is respected. The shadow walker uses the same logic as ripgrep (ignore crate) with require_git(false), so non-git workspaces are handled too. Hidden files (.cargo/, .git/, etc.) are skipped.

This feature complements rather than replaces rust-analyzer: keeping rust-analyzer running alongside (with its own diagnostics turned off, see the editor setup sections) gives you completion, hover, and go-to-definition on top of bacon-ls's live diagnostics.

Bacon backend options

Reads diagnostics from the

Core symbols most depended-on inside this repo

mk_file
called by 27
src/shadow.rs
update_from_json_obj
called by 13
src/lib.rs
project_span
called by 9
src/native.rs
send
called by 8
tests/cargo_backend.rs
path_to_file_uri
called by 7
src/lib.rs
pump
called by 7
tests/cargo_backend.rs
empty_config
called by 7
tests/cargo_backend.rs
read_until_cr_or_lf
called by 6
src/native.rs

Shape

Function 156
Method 66
Class 25
Enum 5

Languages

Rust98%
TypeScript2%

Modules by API surface

src/lib.rs95 symbols
src/bacon.rs49 symbols
src/native.rs48 symbols
src/shadow.rs21 symbols
tests/cargo_backend.rs18 symbols
src/lsp.rs12 symbols
vscode/extension.ts4 symbols
tests/lsp_restart.rs4 symbols
src/main.rs1 symbols

For agents

$ claude mcp add bacon-ls \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact