| Crates.io | bkmr-lsp |
| lib.rs | bkmr-lsp |
| version | 0.8.0 |
| created_at | 2025-06-08 16:58:09.767294+00 |
| updated_at | 2025-08-21 14:10:41.626145+00 |
| description | Language Server Protocol implementation for bkmr snippet manager |
| homepage | https://github.com/sysid/bkmr-lsp |
| repository | https://github.com/sysid/bkmr-lsp |
| max_upload_size | |
| id | 1705066 |
| size | 212,894 |
Language Server Protocol (LSP) implementation for bkmr snippet management.
bkmr-lsp provides snippet completion for bkmr snippets in any LSP-compatible editor. Snippets are automatically interpolated, delivering processed content rather than raw templates. Additionally it respects snippet tabstops, etc.
Key Features:
--interpolate flagInstall directly from crates.io using cargo:
cargo install bkmr-lsp
git clone https://github.com/sysid/bkmr-lsp
cd bkmr-lsp
cargo build --release
cp target/release/bkmr-lsp /usr/local/bin/
Ensure bkmr (>= 4.24.0) is installed and contains snippets:
# Install bkmr if not present
cargo install bkmr
# Verify version
bkmr --version # Must be >= 4.24.0
# Add test snippet
bkmr add "console.log('Hello World');" javascript,test --type snip --title "JS Hello"
# Disable bkmr template interpolation
bkmr-lsp --no-interpolation
# Show help and available options
bkmr-lsp --help
# Show version information
bkmr-lsp --version
Snippets tagged with "plain" are treated as plain text, preventing LSP clients from interpreting snippet syntax like $1, ${2:default}, etc.
Some content should be inserted literally without any LSP snippet processing:
${COMPANY} or ${VERSION} that should appear as literal text$HOME that shouldn't be treated as LSP placeholders# Create a plain text snippet
bkmr add 'Config: ${DATABASE_URL}\nUser: ${USERNAME}' plain,_snip_ --title "Config Template"
# Regular snippet (with LSP processing)
bkmr add 'function ${1:name}() {\n ${2:// implementation}\n}' javascript,_snip_ --title "JS Function"
Default behavior: bkmr-lsp uses the --interpolate flag when calling the bkmr CLI, which processes template variables and functions before serving snippets to LSP clients.
bkmr templates support dynamic content generation through:
{{now}}, {{clipboard}}, {{file_stem}}, etc.{{date("+%Y-%m-%d")}}, {{path_relative()}}, etc.{{#if condition}}...{{/if}}Example transformation:
# Template stored in bkmr:
println!("Generated on {{now}} in {{file_stem}}");
// TODO: {{clipboard}}
# With interpolation (default):
println!("Generated on 2024-01-15 14:30:22 in main");
// TODO: copied text from clipboard
# Without interpolation (--no-interpolation):
println!("Generated on {{now}} in {{file_stem}}");
// TODO: {{clipboard}}
Use --no-interpolation when:
Install an LSP extension and add to settings.json:
{
"languageServerExample.servers": {
"bkmr-lsp": {
"command": "bkmr-lsp",
"args": [],
"filetypes": ["rust", "javascript", "typescript", "python", "go", "java", "c", "cpp", "html", "css", "scss", "ruby", "php", "swift", "kotlin", "shell", "yaml", "json", "markdown", "xml", "vim"]
}
}
}
To disable template interpolation:
{
"languageServerExample.servers": {
"bkmr-lsp": {
"command": "bkmr-lsp",
"args": ["--no-interpolation"],
"filetypes": ["rust", "javascript", "typescript", "python", "go", "java", "c", "cpp", "html", "css", "scss", "ruby", "php", "swift", "kotlin", "shell", "yaml", "json", "markdown", "xml", "vim"]
}
}
}
if executable('bkmr-lsp')
augroup LspBkmr
autocmd!
autocmd User lsp_setup call lsp#register_server({
\ 'name': 'bkmr-lsp',
\ 'cmd': {server_info->['bkmr-lsp']},
\ 'allowlist': ['rust', 'javascript', 'typescript', 'python', 'go', 'java', 'c', 'cpp', 'html', 'css', 'scss', 'ruby', 'php', 'swift', 'kotlin', 'shell', 'yaml', 'json', 'markdown', 'xml', 'vim'],
\ })
augroup END
endif
To disable template interpolation:
if executable('bkmr-lsp')
augroup LspBkmr
autocmd!
autocmd User lsp_setup call lsp#register_server({
\ 'name': 'bkmr-lsp',
\ 'cmd': {server_info->['bkmr-lsp', '--no-interpolation']},
\ 'allowlist': ['rust', 'javascript', 'typescript', 'python', 'go', 'java', 'c', 'cpp', 'html', 'css', 'scss', 'ruby', 'php', 'swift', 'kotlin', 'shell', 'yaml', 'json', 'markdown', 'xml', 'vim'],
\ })
augroup END
endif
Basic setup:
require'lspconfig'.bkmr_lsp.setup{
cmd = { "bkmr-lsp" },
filetypes = { "rust", "javascript", "typescript", "python", "go", "java", "c", "cpp", "html", "css", "scss", "ruby", "php", "swift", "kotlin", "shell", "yaml", "json", "markdown", "xml", "vim" },
}
To disable template interpolation:
require'lspconfig'.bkmr_lsp.setup{
cmd = { "bkmr-lsp", "--no-interpolation" },
filetypes = { "rust", "javascript", "typescript", "python", "go", "java", "c", "cpp", "html", "css", "scss", "ruby", "php", "swift", "kotlin", "shell", "yaml", "json", "markdown", "xml", "vim" },
}
(with-eval-after-load 'lsp-mode
(add-to-list 'lsp-language-id-configuration '(".*" . "text"))
(lsp-register-client
(make-lsp-client :new-connection (lsp-stdio-connection "bkmr-lsp")
:major-modes '(text-mode)
:server-id 'bkmr-lsp)))
rust, python, javascript)Supported Languages:
| Language | File Extensions | LSP Language ID |
|---|---|---|
| Rust | .rs |
rust |
| Python | .py |
python |
| JavaScript | .js |
javascript |
| TypeScript | .ts, .tsx |
typescript |
| Go | .go |
go |
| Java | .java |
java |
| C/C++ | .c, .cpp, .cc |
c, cpp |
| Shell | .sh, .bash |
shell, sh |
| YAML | .yaml, .yml |
yaml |
| JSON | .json |
json |
| Markdown | .md |
markdown |
| And many more... |
Setting up language-specific snippets:
# Tag snippets with language identifiers
bkmr add 'export $HOME' _snip_,sh,plain --title export-home # plain: do not interpret $HOME as snippet variable, keep literal
bkmr add '{{ "date -u +%Y-%m-%d %H:%M:%S" | shell }}' _snip_,universal --title date # uses bkmr server-side interpolation
bkmr queries, generated by the LSP server for different languages:
# Shell file:
(tags:sh AND tags:"_snip_") OR (tags:universal AND tags:"_snip_")
# With word filter:
((tags:rust AND tags:"_snip_") OR (tags:universal AND tags:"_snip_")) AND metadata:hello*
Universal snippets are written in Rust syntax and get automatically translated:
// comment becomes # comment// comment becomes <!-- comment --> (4 spaces) becomes tabs for Go, 2 spaces for JavaScript, etc./* comment */ adapts to target language syntaxSee UNIVERSAL_SNIPPETS.md for complete documentation.
The server provides LSP commands for additional functionality:
bkmr.insertFilepathCommentInsert the relative filepath as a comment at the beginning of the file.
Example output:
// src/backend.rs <--- inserted at top of file
use tower_lsp::LanguageServer;
Neovim Configuration:
if vim.fn.executable('bkmr-lsp') == 1 then
local lspconfig = require('lspconfig')
local configs = require('lspconfig.configs')
-- Register bkmr_lsp if not already registered
if not configs.bkmr_lsp then
configs.bkmr_lsp = {
default_config = {
cmd = { 'bkmr-lsp' },
filetypes = { 'markdown', 'text', 'lua', 'python', 'rust', 'javascript', 'typescript', 'sh', 'bash', 'yaml', 'toml', 'json' },
root_dir = function(fname)
return lspconfig.util.find_git_ancestor(fname) or vim.fn.getcwd()
end,
settings = {},
},
}
end
lspconfig.bkmr_lsp.setup({
capabilities = require('cmp_nvim_lsp').default_capabilities(),
settings = {
bkmr = {
enableIncrementalCompletion = false
}
},
on_attach = function(client, bufnr)
-- Create bkmr-lsp custom commands
vim.api.nvim_create_user_command('BkmrInsertPath', function()
-- Use the modern LSP API and correct argument format
vim.lsp.buf_request(0, 'workspace/executeCommand', {
command = "bkmr.insertFilepathComment",
arguments = {
vim.uri_from_bufnr(0) -- Pass URI as string, not object
}
}, function(err, result)
if err then
vim.notify("Error executing bkmr command: " .. tostring(err), vim.log.levels.ERROR)
elseif result then
-- The server returns a WorkspaceEdit that should be applied
vim.lsp.util.apply_workspace_edit(result, client.offset_encoding)
end
end)
end, { desc = "Insert filepath comment via bkmr-lsp" })
-- Additional bkmr-lsp commands can be added here
end
})
end
Usage in Other LSP Clients: Most LSP clients can execute this command programmatically. For IntelliJ Platform IDEs, use the bkmr-intellij-plugin which provides UI integration.
bkmr search --json --interpolate 'tags:"_snip_"'bkmr --versionecho '{"jsonrpc":"2.0","method":"initialize","id":1,"params":{}}' | bkmr-lspIf LSP snippet navigation ($1, ${2:default}) doesn't work:
Problem: Snippet might be tagged as "plain" or have malformed placeholder syntax Solutions:
$1, $2, $3${1:default text}, ${2:another default}${1|option1,option2,option3|}Update bkmr to version 4.24.0 or later:
cargo install bkmr --force
which bkmr-lspbkmr-lsp follows Clean Architecture principles with clear separation of concerns:
For detailed architecture documentation, see DEVELOPMENT.md.
cargo build --release
The project includes comprehensive testing with 84 tests covering:
# Run all tests
cargo test
# Run specific test categories
cargo test test_backend # Unit tests
cargo test test_lsp_integration # LSP protocol tests
cargo test integration_test # bkmr CLI integration
The project includes several development and testing scripts:
Build and Development:
# Quick development cycle
make all-fast # Debug build + install (symlinked)
make build-fast # Debug build only
make install-debug # Install debug version to ~/bin
# Release builds
make build # Release build with optimizations
make install # Install release version
# Code quality
make format # Format code with cargo fmt
make lint # Run clippy with fixes
make test # Run all tests
Demo and Testing Scripts:
# Language filtering demonstration
./scripts/demo_language_filtering.py
# Completion behavior testing
./scripts/test_completion_behavior.py
# Text replacement testing
./scripts/test_text_replacement.py
# Integration testing
./scripts/integration_test.sh
# LSP protocol testing
./scripts/test_lsp.py
./scripts/test_lsp.sh
Development Logging:
# View LSP server logs during development
make log-lsp # Tail LSP server logs (JSON formatted)
make log-plugin # Tail IntelliJ plugin logs (filtered)
# Clear logs and reset development environment
make init # Clear logs and reset
These scripts demonstrate various features including language detection, completion behavior, and universal snippet translation.
The project includes comprehensive tests for universal snippet functionality:
# Run all tests including universal snippet translation
cargo test
# Test only universal snippet features
cargo test universal
cargo test fts_query
cargo test rust_pattern
Testing with real data:
# Add a universal snippet for testing
bkmr add -t universal -t _snip_ '// Header: {{ title }}
/*
Author: {{ author }}
*/
fn example() {
// TODO: implement
}' 'Universal function header'
# Test FTS query manually
bkmr search --json --interpolate '(tags:python AND tags:"_snip_") OR (tags:universal AND tags:"_snip_")'
The LSP server automatically adjusts log levels based on the execution context:
Manual log level control:
# Enable debug logging (will appear as ERRORs in LSP client logs)
RUST_LOG=debug bkmr-lsp
# Completely disable logging
BKMR_LSP_NO_LOG=1 bkmr-lsp
# Log to file for debugging
RUST_LOG=debug bkmr-lsp 2>/tmp/bkmr-lsp.log
Debug log entries for language filtering:
Document opened: file:///example.rs (language: rust)
Document language ID: Some("rust")
Using language filter: rust
Note: LSP clients (like Neovim) treat all stderr output as errors, so debug messages will appear under ERROR in client logs. This is normal LSP behavior.
The LSP server implements efficient language-aware filtering and universal snippet processing:
Core Components:
textDocument/didOpen eventsProcessing Flow:
Document Open → Language ID Cache → Completion Request → FTS Query Build →
bkmr CLI Call → Universal Translation → LSP Response
Example FTS Queries:
# Language-specific with universal fallback
(tags:python AND tags:"_snip_") OR (tags:universal AND tags:"_snip_")
# With word-based filtering
((tags:rust AND tags:"_snip_") OR (tags:universal AND tags:"_snip_")) AND metadata:config*
textDocument/didOpen language ID--interpolate flag