Skip to content

polarmutex/beancount-language-server

Repository files navigation

Beancount Language Server

License GitHub release (latest by date) Crates.io

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.

nixos

✨ Features

🚀 Currently Implemented

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.

📋 Completion Types

  • Accounts: Autocomplete account names with hierarchy support (Assets:Checking)
  • Payees: Previously used payee names
  • Dates: Smart date completion (today, this month, previous month, next month)
  • Narration: Previously used transaction descriptions
  • Tags: Complete hashtags (#vacation)
  • Links: Complete links (^receipt-123)
  • Transaction Types: txn, balance, open, close, etc.

🔮 Planned Features

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
Semantic Highlighting Advanced syntax highlighting with semantic information Medium
Code Actions Quick fixes, refactoring, auto-balance transactions Medium
Inlay Hints Show computed balances, exchange rates, running totals Low
Signature Help Help with transaction syntax and directive parameters Low
Workspace Symbols Find accounts, payees, commodities across all files Low

📦 Installation

Method 1: Cargo (Recommended)

cargo install beancount-language-server

Method 2: GitHub Releases (Pre-built Binaries)

Download the latest release for your platform from the releases page.

Supported Platforms:

  • Linux (x86_64, aarch64, loongarch64)
  • macOS (x86_64, aarch64)
  • Windows (x86_64)

Method 3: Homebrew (macOS/Linux)

brew install beancount-language-server

Method 4: Nix

# Using nix-env
nix-env -iA nixpkgs.beancount-language-server

# Using nix shell
nix shell nixpkgs#beancount-language-server

# Development environment
nix develop

Method 5: Build from Source

git clone https://github.com/polarmutex/beancount-language-server.git
cd beancount-language-server
cargo build --release

The binary will be available at target/release/beancount-language-server.

🔧 Requirements

Required

  • Beancount: Install the Python beancount package for diagnostics
    pip install beancount

Optional

  • Bean-format: The language server includes built-in formatting that's fully compatible with bean-format. Installing bean-format is optional for comparison or standalone use
    pip install bean-format

⚙️ Configuration

The language server accepts configuration via LSP initialization options:

{
  "journal_file": "/path/to/main.beancount",
  "formatting": {
    "prefix_width": 30,
    "num_width": 10,
    "currency_column": 60,
    "account_amount_spacing": 2,
    "number_currency_spacing": 1
  }
}

Configuration Options

Option Type Description Default
journal_file string Path to the main beancount journal file None

Formatting Options

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

Formatting Modes

Default Mode (no currency_column specified):

  • Accounts are left-aligned
  • Numbers are right-aligned with consistent end positions
  • Behaves like bean-format with no special options

Currency Column Mode (currency_column specified):

  • Currencies are aligned at the specified column
  • Numbers are positioned to place currencies at the target column
  • Equivalent to bean-format --currency-column N

Examples

Basic 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) - default
  • 2: Two spaces (100.00 USD)

Combined options:

{
  "formatting": {
    "prefix_width": 30,
    "currency_column": 65,
    "account_amount_spacing": 3,
    "number_currency_spacing": 1
  }
}

🖥️ Editor Setup

Visual Studio Code

  1. Install the Beancount extension from the marketplace
  2. Configure in settings.json:
    {
      "beancount.journal_file": "/path/to/main.beancount",
      "beancount.formatting": {
        "prefix_width": 30,
        "currency_column": 60,
        "number_currency_spacing": 1
      }
    }

Neovim

Using nvim-lspconfig:

local lspconfig = require('lspconfig')

lspconfig.beancount.setup({
  init_options = {
    journal_file = "/path/to/main.beancount",
    formatting = {
      prefix_width = 30,
      currency_column = 60,
      number_currency_spacing = 1,
    },
  },
})

File type detection: Ensure beancount files are detected. Add to your config:

vim.filetype.add({
  extension = {
    beancount = "beancount",
    bean = "beancount",
  },
})

Helix

Add to your languages.toml:

[language-server.beancount-language-server]
command = "beancount-language-server"
args = ["--stdio"]

[language-server.beancount-language-server.config]
journal_file = "/path/to/main.beancount"

[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" }]

Emacs

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
    (lambda () (list :journal_file "/path/to/main.beancount"
                     :formatting '(:prefix_width 30 :currency_column 60 :number_currency_spacing 1))))))

Vim

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': {
        \   'journal_file': '/path/to/main.beancount',
        \   'formatting': {
        \     'prefix_width': 30,
        \     'currency_column': 60,
        \     'number_currency_spacing': 1
        \   }
        \ }
    \ })
endif

Sublime Text

Using LSP:

Add to LSP settings:

{
  "clients": {
    "beancount-language-server": {
      "enabled": true,
      "command": ["beancount-language-server"],
      "selector": "source.beancount",
      "initializationOptions": {
        "journal_file": "/path/to/main.beancount",
        "formatting": {
          "prefix_width": 30,
          "currency_column": 60,
          "number_currency_spacing": 1
        }
      }
    }
  }
}

🏗️ Architecture

High-Level Overview

┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│     Editor      │◄──►│  LSP Server     │◄──►│   Beancount     │
│                 │    │                 │    │   (Python)      │
│ - VSCode        │    │ - Completion    │    │ - Validation    │
│ - Neovim        │    │ - Formatting    │    │ - Parsing       │
│ - Helix         │    │ - Diagnostics   │    │ - Bean-check    │
│ - Emacs         │    │ - Tree-sitter   │    │                 │
└─────────────────┘    └─────────────────┘    └─────────────────┘

Core Components

  • LSP Server: Main Rust application handling LSP protocol
  • Tree-sitter Parser: Fast, incremental parsing of Beancount syntax
  • Completion Engine: Smart autocompletion with context awareness
  • Diagnostic Provider: Integration with beancount Python for validation
  • Formatter: Code formatting fully compatible with bean-format, supporting prefix-width, num-width, and currency-column options

Project Structure

beancount-language-server/
├── crates/lsp/           # Main LSP server implementation
│   ├── src/
│   │   ├── handlers.rs   # LSP request/notification handlers
│   │   ├── providers/    # Feature providers (completion, diagnostics, etc.)
│   │   └── server.rs     # Core LSP server logic
├── vscode/               # VS Code extension
├── python/               # Python integration utilities
└── flake.nix            # Nix development environment

🛠️ Development

Prerequisites

  • Rust (stable toolchain)
  • Python with beancount
  • Node.js (for VS Code extension)

Development Environment

Using Nix (Recommended):

nix develop

Manual Setup:

# Install Rust dependencies
cargo build

# Install Node.js dependencies (for VS Code extension)
cd vscode && npm install

# Install development tools
cargo install cargo-watch

Running Tests

# Run all tests
cargo test

# Run with coverage
cargo llvm-cov --all-features --locked --workspace --lcov --output-path lcov.info

# Run specific test
cargo test test_completion

Code Quality

# Format code
cargo fmt

# Lint code
cargo clippy --all-targets --all-features

# Check formatting
cargo fmt -- --check

Development Workflow

  1. Make changes to the Rust code
  2. Test locally with cargo test
  3. Run LSP server in development mode:
    cargo run --bin beancount-language-server
  4. Test with editor by configuring it to use the local binary

VS Code Extension Development

cd vscode
npm run build      # Build extension
npm run watch      # Watch for changes
npm run package    # Package extension

Release Process

The project uses cargo-dist for automated releases:

  1. Tag a release: git tag v1.0.0 && git push --tags
  2. GitHub Actions automatically builds and publishes:
    • Binaries for all supported platforms
    • Crates.io release
    • GitHub release with assets

🤝 Contributing

Contributions are welcome! Here are some ways to help:

🐛 Bug Reports

  • Search existing issues first
  • Include beancount file examples that trigger the bug
  • Provide editor and OS information

💡 Feature Requests

  • Check the planned features list
  • Describe the use case and expected behavior
  • Consider the LSP specification constraints

🔧 Code Contributions

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Make your changes with tests
  4. Ensure code quality: cargo fmt && cargo clippy && cargo test
  5. Commit your changes (git commit -m 'Add amazing feature')
  6. Push to the branch (git push origin feature/amazing-feature)
  7. Open a Pull Request

🎯 Good First Issues

Look for issues labeled good-first-issue:

  • Add new completion types
  • Improve error messages
  • Add editor configuration examples
  • Improve documentation

📚 Resources

📄 License

This project is licensed under the MIT License - see the LICENSE file for details.

🙏 Acknowledgments

  • Beancount - The amazing double-entry bookkeeping language
  • Tree-sitter - Incremental parsing framework
  • LSP - Language Server Protocol specification
  • Twemoji - Emoji graphics used in the icon

Happy Beancounting! 📊✨

About

A Language Server Protocol (LSP) for beancount files

Resources

License

Security policy

Stars

Watchers

Forks

Sponsor this project

 

Packages

No packages published

Contributors 16