themoritz/turbobean
Beancount implementation with a focus on speed and ease of use
master6274eebace9a6a82ce182e24468fef88e0b95f373cc6a4a1502fdea2e4d3869099e39d9c5e8b98a5
An implementation of Beancount with a focus on speed and ease of use.
I love Beancount, but my journal is growing and the existing ecosystem with its Python based implementation is too slow. Also, there's no easy to set up LSP which prevents me from editing my .bean files in Vim. This implementation aims to fix both.
The philosopy is:
Speed (processes huge files instantly)
LSP Server
Jump to account open
Hover account (before + after balance)
Auto completion (accounts, tags, links)
Highlight account
Highlight tags + links
Syntax highlighting via semantic tokens
Rename account
Display interpolated values inline
Web UI (similar to fava)
File Watcher (instant reloads)
MacOS
Windows
Linux
Journal
Balance Sheet
Income Statement
Filter language
Display errors
Lua Plugins
Pretty formatter
Protobuf Output
Go to the latest
release, pick your CPU
architecture and operating system, then download and extract the tarball/zip to
somewhere on your $PATH.
Install the Zig compiler. Then (assuming you have a Unix
system and ~/.local/bin is on your $PATH):
zig build --release=safe -Dembed-static --prefix ~/.local
turbobean serve <project_root>.bean to launch a server that serves the
Web UI.http://localhost:8080 in your browser.g key to fuzzy-navigate (e.g. balance sheet, income statement, journal).Any editor that supports LSP should work. You just need to tell it to use
the turbobean lsp command to start the server (it always uses stdio for
transport).
For projects spanning multiple files, you can define the root .bean file as
follows: Create a turbobean.config file in the workspace folder (e.g., next
to your .git folder) with the following content:
root = main.bean
Put this into your nvim-lspconfig's config function:
local lspconfig = require 'lspconfig'
require('lspconfig.configs').turbobean = {
default_config = {
cmd = {
'bash',
'-c',
'turbobean lsp 2> >(tee turbobean.log >&2)',
},
filetypes = { 'beancount', 'bean' },
root_dir = require('lspconfig.util').root_pattern 'turbobean.config',
},
}
lspconfig.turbobean.setup {}
Disable treesitter since it interferes with syntax highlighting coming from the LSP's semantic tokens feature:
return {
'nvim-treesitter/nvim-treesitter',
opts = {
highlight = {
enable = true,
disable = { 'beancount' },
},
},
}
Maybe there's a way we can keep treesitter enabled and then overwrite with the sematic tokens info?
Use the extension in this repo:
cd vscode
npm i
code .
Then press F5, or go to debugging and click "Run Extension".
Minimal Emacs 30 config based on eglot:
;; 1. Define the major mode
(define-derived-mode beancount-mode prog-mode "Beancount"
"Major mode for editing Beancount files."
(eglot-semtok-font-lock-init) ; For semantic tokens
(eglot-ensure)) ; start Eglot automatically
(add-to-list 'auto-mode-alist '("\\.bean\\'" . beancount-mode))
;; 2. Tell Eglot how to start your server
(with-eval-after-load 'eglot
(add-to-list 'eglot-server-programs
'(beancount-mode . ("~/.local/bin/turbobean" "lsp"))))
;; 3. Tell Eglot to use semantic tokens
;; (from https://codeberg.org/harald/eglot-supplements/src/branch/main/eglot-semtok.el)
(require 'eglot-semtok "~/.emacs.d/eglot-semtok.el")
(with-eval-after-load 'eglot
;; start eglot-semtok once we have a server connection
(add-hook 'eglot-connect-hook 'eglot-semtok-on-connected))
In your ~/.config/helix/languages.toml:
[language-server.turbobean]
command = "turbobean"
args = ["lsp"]
[[language]]
name = "bean"
scope = "source.bean"
grammar = "beancount"
file-types = ["bean"]
language-servers = ["turbobean"]
I don't know how to get Helix to use the semantic tokens feature so I copied
the existing Beancount highlight queries (from
https://github.com/helix-editor/helix/blob/master/runtime/queries/beancount/highlights.scm
to ~/.config/helix/runtime/queries/bean/highlights.scm).
Aims to be compatible with Beancount as much as possible, following some ideas from Beancount Vnext: Goals & Design, notably Beancount - Vnext: Booking Rules Redesign. This is currently implemented in a non-backwards-compatible way.
The balancing algorithm doesn't automatically insert multiple postings to the same account. For example, the following transaction doesn't balance:
2023-10-30 * "Cash Distribution"
Assets:Cash -92.08 EUR
Assets:Cash -794.49 USD
Expenses:Trips:Car 600.00 USD
Expenses:Food:Out
You have to insert a second Expenses:Food:Out posting so that the USD and
EUR amounts can be put there. This is so that the editor can properly show
the inserted amounts inline.
The booking rules design is not fully formed yet. Right now there is the distinction between "booked" and "plain" accounts. Commodities can only be bought in "booked" accounts, which is not great but simplifies implementation.
zig build
zig build run
zig build run -- serve foo.bean
I'm using watchexec for automatic rebuilds on file change.
When iterating on Zig tests in a particular file:
watchexec -e zig -- zig test src/lexer.zig --test-filter "windows"
When iterating on the server (zig code or template):
watchexec -r -e zig,html -- zig build run -- serve test.bean
(page needs to be reloaded manually in the browser)
When iterating on JS or CSS files, just reload the page while the server is running.
zig build test
zig test src/date.zig
Prerequisite: Install Node
cd vscode
npm i
npm run test
Prerequisite: Install Bun
cd tests/puppeteer
bun i
bun test