bkmr-lsp
Language Server Protocol (LSP) implementation for bkmr snippet management.
Overview
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:
- Language-aware filtering: Snippets are filtered by file type (e.g., Rust files get only Rust snippets)
- Universal snippets: Language-agnostic snippets with natural Rust syntax that automatically adapt to target languages
- Automatic interpolation: Templates are processed using bkmr's
--interpolateflag - Plain text snippets: Snippets tagged with "plain" are treated as plain text without LSP snippet processing
- Additional LSP commands: Filepath comment insertion with automatic language detection
Requirements
- bkmr: Version 4.24.0 or later
- LSP Client: Any LSP-compatible editor (VS Code, Vim/Neovim, Emacs, etc.)
Installation
Standard Installation
Install directly from crates.io using cargo:
From Source
Prerequisites
Ensure bkmr (>= 4.24.0) is installed and contains snippets:
# Install bkmr if not present
# Verify version
# Add test snippet
CLI Options
# Disable bkmr template interpolation
# Show help and available options
# Show version information
Plain Text Snippets
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:
- Documentation templates: Contains
${COMPANY}or${VERSION}that should appear as literal text - Configuration files: Raw templates with placeholder syntax
- Shell scripts: Variables like
$HOMEthat shouldn't be treated as LSP placeholders
Usage
# Create a plain text snippet
# Regular snippet (with LSP processing)
Template Interpolation
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.
Why?
bkmr templates support dynamic content generation through:
- Variables:
{{now}},{{clipboard}},{{file_stem}}, etc. - Functions:
{{date("+%Y-%m-%d")}},{{path_relative()}}, etc. - Conditional logic:
{{#if condition}}...{{/if}}
Example transformation:
# Template stored in bkmr:
);
# With interpolation (default):
);
# Without interpolation (--no-interpolation):
);
Use --no-interpolation when:
- You want to see the raw template syntax in completions
- You prefer to handle template processing manually after snippet insertion
- Debugging template syntax or variables
Configuration
VS Code
Install an LSP extension and add to settings.json:
To disable template interpolation:
Vim/Neovim with vim-lsp
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
Neovim with nvim-lspconfig
Basic setup:
require..
To disable template interpolation:
require..
Emacs with lsp-mode
(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)))
Usage
Language-Aware Filtering
- Language Detection: When you open a file, the LSP client sends the language ID (e.g.,
rust,python,javascript) - Smart Filtering: Shows snippets tagged with your current language PLUS universal snippets
- FTS Query: Builds optimized full-text search queries for bkmr
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 queries, generated by the LSP server for different languages:
# Shell file:
())
# With word filter:
Universal Snippets
Universal snippets are written in Rust syntax and get automatically translated:
- Python:
// commentbecomes# comment - HTML:
// commentbecomes<!-- comment --> - Indentation:
(4 spaces) becomes tabs for Go, 2 spaces for JavaScript, etc. - Block comments:
/* comment */adapts to target language syntax
See UNIVERSAL_SNIPPETS.md for complete documentation.
LSP Commands
The server provides LSP commands for additional functionality:
bkmr.insertFilepathComment
Insert the relative filepath as a comment at the beginning of the file.
Example output:
// src/backend.rs <--- inserted at top of file
use LanguageServer;
Neovim Configuration:
if vim.. == 1
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.
Troubleshooting
No Completions Appearing
- Verify bkmr works:
bkmr search --json --interpolate 'tags:"_snip_"' - Check bkmr version:
bkmr --version - Test LSP server:
echo '{"jsonrpc":"2.0","method":"initialize","id":1,"params":{}}' | bkmr-lsp
LSP Placeholders Not Working
If LSP snippet navigation ($1, ${2:default}) doesn't work:
Problem: Snippet might be tagged as "plain" or have malformed placeholder syntax Solutions:
- Check if snippet is plain: Plain text snippets (tagged with "plain") don't support LSP placeholders
- Verify placeholder syntax:
- Simple tabstops:
$1,$2,$3 - Placeholders:
${1:default text},${2:another default} - Choices:
${1|option1,option2,option3|}
- Simple tabstops:
- Remove plain tag: If you want LSP processing, remove "plain" from snippet tags
Raw Templates in Completions
Update bkmr to version 4.24.0 or later:
LSP Server Not Starting
- Verify binary is in PATH:
which bkmr-lsp - Check editor LSP configuration
- Review editor LSP logs for errors
Architecture
bkmr-lsp follows Clean Architecture principles with clear separation of concerns:
- Domain Layer: Pure business models for snippets, languages, and completion
- Repository Layer: Data access abstraction with Repository pattern for bkmr CLI integration
- Service Layer: Business logic orchestration with dependency injection
- Infrastructure Layer: LSP protocol implementation with clean separation
For detailed architecture documentation, see DEVELOPMENT.md.
Development
Building
Testing
The project includes comprehensive testing with 84 tests covering:
- Unit tests for domain logic and services
- Integration tests with real bkmr CLI execution
- LSP protocol tests with actual server instances
- Mock repositories for isolated testing
# Run all tests
# Run specific test categories
Development Scripts
The project includes several development and testing scripts:
Build and Development:
# Quick development cycle
# Release builds
# Code quality
Demo and Testing Scripts:
# Language filtering demonstration
# Completion behavior testing
# Text replacement testing
# Integration testing
# LSP protocol testing
Development Logging:
# View LSP server logs during development
# Clear logs and reset development environment
These scripts demonstrate various features including language detection, completion behavior, and universal snippet translation.
Testing Universal Snippets
The project includes comprehensive tests for universal snippet functionality:
# Run all tests including universal snippet translation
# Test only universal snippet features
Testing with real data:
# Add a universal snippet for testing
# Test FTS query manually
Logging
The LSP server automatically adjusts log levels based on the execution context:
- LSP mode (when run by LSP clients): Defaults to ERROR level to avoid noise in client logs
- Terminal mode (when run manually): Defaults to WARN level for development
Manual log level control:
# Enable debug logging (will appear as ERRORs in LSP client logs)
RUST_LOG=debug
# Completely disable logging
BKMR_LSP_NO_LOG=1
# Log to file for debugging
RUST_LOG=debug
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.
Implementation Details
Architecture Overview
The LSP server implements efficient language-aware filtering and universal snippet processing:
Core Components:
- Language ID Capture: Captures and caches language IDs from
textDocument/didOpenevents - FTS Query Building: Builds optimized Full Text Search queries combining language-specific and universal snippets
- Universal Translation: Automatically translates Rust syntax patterns to target languages using regex-based processing
- Cache Management: Maintains document language state and cleans up on document close
Processing 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
())
# With word-based filtering
Protocol Support
- LSP Version: 3.17
- Features:
- Manual completion triggered via Ctrl+Space with word-based filtering
- Language-aware snippet filtering using
textDocument/didOpenlanguage ID - Universal snippets with natural Rust syntax translation
- Template interpolation via bkmr
--interpolateflag - Plain text snippets for literal content insertion (tag with "plain")
- FTS-based queries for optimal snippet retrieval
- Live snippet fetching with bkmr CLI integration
- LSP commands for filepath comment insertion with language detection
Contributing
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Submit a pull request
Related Projects
- bkmr - Command-line bookmark and snippet manager
- vim-bkmr-lsp - Vim plugin for bkmr-lsp