| Crates.io | beancount-language-server |
| lib.rs | beancount-language-server |
| version | 1.8.1 |
| created_at | 2021-11-11 15:38:08.740309+00 |
| updated_at | 2026-01-25 03:03:34.151906+00 |
| description | A Language Server Protocol implementation for Beancount, providing rich editing features like completions, diagnostics, and formatting |
| homepage | https://github.com/polarmutex/beancount-language-server |
| repository | https://github.com/polarmutex/beancount-language-server |
| max_upload_size | |
| id | 480208 |
| size | 648,572 |
A Language Server Protocol (LSP) implementation for Beancount, the double-entry bookkeeping language. This provides rich editing features like completions, diagnostics, formatting, and more for Beancount files in your favorite editor.
| LSP Feature | Description | Status |
|---|---|---|
| Completions | Smart autocompletion for accounts, payees, dates, narration, tags, links, and transaction types | â |
| Diagnostics | Real-time error checking and validation via beancount Python integration | â |
| Formatting | Document formatting compatible with bean-format, with support for prefix-width, num-width, and currency-column options |
â |
| Rename | Rename symbols across files | â |
| References | Find all references to accounts, payees, etc. | â |
| Semantic Highlighting | Advanced syntax highlighting with semantic information | â |
| Inlay Hints | Show calculated balancing amounts and unbalanced transaction warnings | â |
Assets:Checking)#vacation)^receipt-123)txn, balance, open, close, etc.Non-intrusive inline annotations that help visualize implicit information:
Examples:
2024-01-15 * "Grocery Store"
Expenses:Food:Groceries 45.23 USD
Assets:Bank:Checking -45.23 USD ; â Shown as inlay hint
2024-01-15 * "Unbalanced Transfer" /* = 500.00 USD â */ ; â Warning shown
Assets:Savings 1000.00 USD
Assets:Checking -500.00 USD
| LSP Feature | Description | Priority |
|---|---|---|
| Hover | Show account balances, transaction details, account metadata | High |
| Go to Definition | Jump to account/payee/commodity definitions | High |
| Document Symbols | Outline view showing accounts, transactions, and structure | High |
| Folding Ranges | Fold transactions, account hierarchies, and multi-line entries | Medium |
| Code Actions | Quick fixes, refactoring, auto-balance transactions | Medium |
| Signature Help | Help with transaction syntax and directive parameters | Low |
| Workspace Symbols | Find accounts, payees, commodities across all files | Low |
cargo install beancount-language-server
Download the latest release for your platform from the releases page.
Supported Platforms:
brew install beancount-language-server
# Using nix-env
nix-env -iA nixpkgs.beancount-language-server
# Using nix shell
nix shell nixpkgs#beancount-language-server
# Development environment
nix develop
git clone https://github.com/polarmutex/beancount-language-server.git
cd beancount-language-server
# Standard build (includes PyO3 embedded Python support by default)
cargo build --release
# Build without PyO3 (minimal binary, requires external bean-check/python)
cargo build --release --no-default-features
The binary will be available at target/release/beancount-language-server.
The language server requires one of the following for validation and diagnostics:
Option 1: PyO3 Embedded (Default - Recommended)
pip install beancount
Option 2: System Python (Fallback)
Option 3: Bean-check Binary (Fallback)
bean-check command-line toolpip install beancount (includes bean-check)Based on comprehensive benchmarks with a 30-line beancount file:
| Method | Average Time | Relative Speed | Availability |
|---|---|---|---|
| PyO3 Embedded (default) | ~838Ξs | 1x (baseline) | Requires Python 3.8+ + beancount |
| System Python | ~50.1ms | 60x slower | Requires Python + beancount |
| Bean-check Binary | ~55.2ms | 66x slower | Requires bean-check binary |
Recommendation: Use PyO3 embedded checker (default in pre-built binaries) for optimal performance.
The language server accepts configuration via LSP initialization options:
{
// Optional: Only needed for multi-file projects with include directives
"journal_file": "/path/to/main.beancount",
"formatting": {
"prefix_width": 30,
"num_width": 10,
"currency_column": 60,
"account_amount_spacing": 2,
"number_currency_spacing": 1
}
}
Note: All configuration is optional. The language server will auto-detect the best checker method (PyO3 â System Python â Bean-check).
| Option | Type | Description | Default |
|---|---|---|---|
journal_file |
string | Path to the main beancount journal file. Optional: Only required if your beancount files use include directives to span multiple files. Single-file projects work without this setting. |
None |
| Option | Type | Description | Default |
|---|---|---|---|
bean_check.method |
string | Validation method: "system", "python-system", or "python-embedded" | None |
bean_check.bean_check_cmd |
string | Path to bean-check binary (for "system" method) | None |
bean_check.python_cmd |
string | Path to Python executable (for Python methods) | None |
Preferred checker order (when bean_check.method is not set):
python-embedded (if built with the feature and available)python-system (if a compatible Python with beancount is available)system (if bean-check is available)Default (no configuration needed):
The language server automatically selects the best available checker method:
No configuration required! Just install Python and beancount.
Override to force a specific method:
Only configure bean_check.method if you need to override auto-detection:
{
"bean_check": {
"method": "system", // Force bean-check binary
"bean_check_cmd": "/usr/local/bin/bean-check"
}
}
{
"bean_check": {
"method": "python-system", // Force Python subprocess
"python_cmd": "/usr/bin/python3"
}
}
{
"bean_check": {
"method": "python-embedded" // Force PyO3 (already default)
}
}
If the PyO3 embedded checker is not working:
Verify Python installation:
python3 --version # Should be 3.8 or higher
Verify beancount installation:
python3 -c "import beancount.loader; print('Beancount OK')"
Check language server logs for PyO3-related messages:
:LspLogInstall beancount if missing:
# System-wide
pip3 install beancount
# User installation (no sudo required)
pip3 install --user beancount
# Virtual environment (recommended)
python3 -m venv ~/.beancount-env
source ~/.beancount-env/bin/activate
pip install beancount
Fallback methods: If PyO3 checker fails, the language server automatically tries:
Check your configuration if you need to explicitly set a method.
| Option | Type | Description | Default | Bean-format Equivalent |
|---|---|---|---|---|
prefix_width |
number | Fixed width for account names (overrides auto-detection) | Auto-calculated | --prefix-width (-w) |
num_width |
number | Fixed width for number alignment (overrides auto-detection) | Auto-calculated | --num-width (-W) |
currency_column |
number | Align currencies at this specific column | None (right-align) | --currency-column (-c) |
account_amount_spacing |
number | Minimum spaces between account names and amounts | 2 | N/A |
number_currency_spacing |
number | Number of spaces between number and currency | 1 | N/A |
Default Mode (no currency_column specified):
bean-format with no special optionsCurrency Column Mode (currency_column specified):
bean-format --currency-column NBasic formatting with auto-detection:
{
"formatting": {}
}
Fixed prefix width (like bean-format -w 25):
{
"formatting": {
"prefix_width": 25
}
}
Currency column alignment (like bean-format -c 60):
{
"formatting": {
"currency_column": 60
}
}
Number-currency spacing control:
{
"formatting": {
"number_currency_spacing": 2
}
}
This controls the whitespace between numbers and currency codes:
0: No space (100.00USD)1: Single space (100.00 USD) - default2: Two spaces (100.00 USD)Combined options:
{
"formatting": {
"prefix_width": 30,
"currency_column": 65,
"account_amount_spacing": 3,
"number_currency_spacing": 1
}
}
settings.json (optional):
{
// Optional: Only needed for multi-file projects with include directives
"beancountLangServer.journalFile": "/path/to/main.beancount",
"beancountLangServer.formatting": {
"prefix_width": 30,
"currency_column": 60,
"number_currency_spacing": 1
}
}
Using nvim.lsp (nvim > 0.11)
lsp/beancount.lua
return {
commands = { "beancount-language-server", "--stdio" },
root_markers = { "main.bean", ".git" },
-- init_options are optional
init_options = {
-- Optional: Only needed for multi-file projects with include directives
journal_file = "main.bean",
},
settings = {
beancount = {
formatting = {
prefix_width = 30,
currency_column = 60,
number_currency_spacing = 1,
}
}
}
}
Using nvim-lspconfig:
local lspconfig = require('lspconfig')
lspconfig.beancount.setup({
-- All init_options are optional
init_options = {
-- Optional: Only needed for multi-file projects with include directives
-- journal_file = "/path/to/main.beancount",
formatting = {
prefix_width = 30,
currency_column = 60,
number_currency_spacing = 1,
},
},
})
-- To override auto-detected checker method:
-- lspconfig.beancount.setup({
-- init_options = {
-- bean_check = {
-- method = "system", -- Force specific method: "python-embedded", "python-system", or "system"
-- },
-- },
-- })
File type detection: Ensure beancount files are detected. Add to your config:
vim.filetype.add({
extension = {
beancount = "beancount",
bean = "beancount",
},
})
Add to your languages.toml:
[language-server.beancount-language-server]
command = "beancount-language-server"
args = ["--stdio"]
# Configuration is optional
[language-server.beancount-language-server.config]
# Optional: Only needed for multi-file projects with include directives
# journal_file = "/path/to/main.beancount"
# Optional: bean_check config (uses python-embedded by default)
# [language-server.beancount-language-server.config.bean_check]
# method = "python-embedded" # or "python-system" or "system"
# Optional: formatting configuration
[language-server.beancount-language-server.config.formatting]
prefix_width = 30
currency_column = 60
number_currency_spacing = 1
[[language]]
name = "beancount"
language-servers = [{ name = "beancount-language-server" }]
Using lsp-mode:
(use-package lsp-mode
:hook (beancount-mode . lsp-deferred)
:config
(lsp-register-client
(make-lsp-client
:new-connection (lsp-stdio-connection "beancount-language-server")
:major-modes '(beancount-mode)
:server-id 'beancount-language-server
:initialization-options
;; All options are optional
(lambda () (list
;; Optional: Only needed for multi-file projects with include directives
;; :journal_file "/path/to/main.beancount"
;; Optional: bean_check config (uses python-embedded by default)
;; :bean_check '(:method "python-embedded")
:formatting '(:prefix_width 30 :currency_column 60 :number_currency_spacing 1))))))
Using vim-lsp:
if executable('beancount-language-server')
au User lsp_setup call lsp#register_server({
\ 'name': 'beancount-language-server',
\ 'cmd': {server_info->['beancount-language-server']},
\ 'allowlist': ['beancount'],
\ 'initialization_options': {
\ 'formatting': {
\ 'prefix_width': 30,
\ 'currency_column': 60,
\ 'number_currency_spacing': 1
\ }
\ }
\ })
" Optional: For multi-file projects with include directives, add:
" \ 'journal_file': '/path/to/main.beancount',
" Optional: To override default checker method, add:
" \ 'bean_check': {'method': 'python-embedded'},
endif
Using LSP:
Add to LSP settings:
{
"clients": {
"beancount-language-server": {
"enabled": true,
"command": ["beancount-language-server"],
"selector": "source.beancount",
// All initializationOptions are optional
"initializationOptions": {
// Optional: Only needed for multi-file projects with include directives
// "journal_file": "/path/to/main.beancount",
"formatting": {
"prefix_width": 30,
"currency_column": 60,
"number_currency_spacing": 1
}
}
}
}
}
âââââââââââââââââââ âââââââââââââââââââ âââââââââââââââââââ
â Editor âââââšâ LSP Server âââââšâ Beancount â
â â â â â (Python) â
â - VSCode â â - Completion â â - Validation â
â - Neovim â â - Formatting â â - Parsing â
â - Helix â â - Diagnostics â â - Bean-check â
â - Emacs â â - Tree-sitter â â â
âââââââââââââââââââ âââââââââââââââââââ âââââââââââââââââââ
beancount-language-server/
âââ crates/lsp/ # Main LSP server implementation
â âââ src/
â â âââ handlers.rs # LSP request/notification handlers
â â âââ providers/ # Feature providers (completion, diagnostics, etc.)
â â âââ checkers/ # Bean-check validation implementations
â â â âââ mod.rs # Strategy trait and factory pattern
â â â âââ system_call.rs # Traditional bean-check binary
â â â âââ pyo3_embedded.rs # PyO3 embedded Python
â â â âââ types.rs # Shared data structures
â â âââ server.rs # Core LSP server logic
âââ vscode/ # VS Code extension
âââ flake.nix # Nix development environment
Using Nix (Recommended):
nix develop
Manual Setup:
# Install Rust dependencies
cargo build
# Install Node.js dependencies (for VS Code extension)
cd vscode && pnpm install
# Install development tools
cargo install cargo-watch
# Run all tests
cargo test
# Run with coverage
cargo llvm-cov --all-features --locked --workspace --lcov --output-path lcov.info
# Run tests with PyO3 feature
cargo test --features python-embedded
# Run specific test
cargo test test_completion
# Format code
cargo fmt
# Lint code
cargo clippy --all-targets --all-features
# Check formatting
cargo fmt -- --check
cargo testcargo run --bin beancount-language-server
cd vscode
pnpm run build # Build extension
pnpm run watch # Watch for changes
pnpm run package # Package extension
git tag v1.0.0 && git push --tagsContributions are welcome! Here are some ways to help:
git checkout -b feature/amazing-feature)cargo fmt && cargo clippy && cargo testgit commit -m 'Add amazing feature')git push origin feature/amazing-feature)Look for issues labeled good-first-issue:
This project is licensed under the MIT License - see the LICENSE file for details.
Happy Beancounting! ðâĻ