| Crates.io | hedl-lsp |
| lib.rs | hedl-lsp |
| version | 1.2.0 |
| created_at | 2026-01-09 00:59:43.238122+00 |
| updated_at | 2026-01-21 03:06:21.598134+00 |
| description | Language Server Protocol (LSP) implementation for HEDL |
| homepage | https://dweve.com |
| repository | https://github.com/dweve/hedl |
| max_upload_size | |
| id | 2031359 |
| size | 649,629 |
Production-grade Language Server Protocol implementation for HEDL—rich IDE integration with diagnostics, completion, navigation, and performance optimization.
Writing HEDL without IDE support means no syntax validation, no autocomplete, no reference checking, no quick navigation. Modern development demands real-time feedback, intelligent suggestions, and seamless navigation. hedl-lsp brings HEDL to every LSP-compatible editor: VS Code, Neovim, Emacs, Sublime Text, and more.
This is a complete LSP server with 10 implemented features, performance optimizations (200ms debouncing, O(1) reference lookups, caching), and comprehensive error handling. Write HEDL with confidence—get instant diagnostics, context-aware completions, hover documentation, and cross-document navigation.
Complete LSP server with production-grade features:
# From source
cargo install hedl-lsp
# Or build locally
cd crates/hedl-lsp
cargo build --release
Binary location: target/release/hedl-lsp
Recommended: Install the official HEDL extension from the VS Code marketplace, which bundles hedl-lsp and provides automatic configuration.
Manual Setup:
hedl-lsp binarysettings.json:{
"hedl.server.path": "/path/to/hedl-lsp",
"hedl.server.trace": "verbose"
}
require('lspconfig').hedl.setup{
cmd = { 'hedl-lsp' },
filetypes = { 'hedl' },
root_dir = function(fname)
return require('lspconfig.util').find_git_ancestor(fname) or vim.fn.getcwd()
end,
settings = {}
}
Auto-start on .hedl files:
vim.cmd([[autocmd BufRead,BufNewFile *.hedl set filetype=hedl]])
(require 'lsp-mode)
(add-to-list 'lsp-language-id-configuration '(hedl-mode . "hedl"))
(lsp-register-client
(make-lsp-client :new-connection (lsp-stdio-connection "hedl-lsp")
:major-modes '(hedl-mode)
:server-id 'hedl-lsp))
(add-hook 'hedl-mode-hook #'lsp)
Add to LSP settings (Preferences: LSP Settings):
{
"clients": {
"hedl-lsp": {
"enabled": true,
"command": ["/path/to/hedl-lsp"],
"selector": "source.hedl"
}
}
}
Add to ~/.config/helix/languages.toml:
[[language]]
name = "hedl"
scope = "source.hedl"
file-types = ["hedl"]
language-server = { command = "hedl-lsp" }
Instant feedback as you type with parse errors and lint warnings:
Parse Errors:
users: @User[id, name # Missing closing bracket
| alice, Alice Smith
Diagnostic:
Error at line 1, column 24:
Parse error: unexpected end of line, expected ']'
Lint Warnings:
%ALIAS: old_api: https://legacy.example.com # Defined but unused
---
users: @User[id, name]
| alice, Alice Smith
Diagnostic:
Warning at line 1:
[unused-alias] Alias 'old_api' is defined but never used
Severity Levels:
7 different completion contexts with intelligent filtering:
Trigger: Start of line in header section
Completions:
%VERSION: Document version (required)
%STRUCT: Define entity schema
%ALIAS: Define variable alias
%NEST: Define parent-child relationship
Trigger: After @ symbol
author: @| # Cursor here
Completions (from defined schemas):
@User
@Post
@Comment
@Organization
Trigger: After @Type:
author: @User:| # Cursor here
Completions (from parsed entities):
@User:alice
@User:bob
@User:carol
Trigger: After : in list declaration
users: | # Cursor here
Completions:
@User[id, name, email]
@Post[id, title, content]
@Comment[id, text, author]
Trigger: Inside matrix row (after |)
users: @User[id, name, active]
| alice, Alice, | # Cursor here
Completions:
^ # Ditto (repeat previous value)
~ # Null
true # Boolean
false # Boolean
@User: # Reference to User entities
Trigger: Start of line in data section
Completions (common keys + inferred from types):
users # List key for User type
posts # List key for Post type
name # Common field
description # Common field
config # Common field
Trigger: After : on data line
api_url: | # Cursor here
Completions:
$api_endpoint # Alias expansion
$version # Alias expansion
@User:alice # Entity reference
@Organization: # Organization references
Performance: Optimized context detection with cached header_end_line for O(1) lookups.
Markdown-formatted documentation on mouse hover:
%STRUCT: User: [id, name, email]
^^^^^^^
Hover Output:
**%STRUCT Directive**
Define entity schema with field names.
**Syntax**: `%STRUCT: TypeName: [field1, field2, ...]`
**Example**:
%STRUCT: User: [id, name, email, created_at]
author: @User:alice
^^^^^^^^^^^
Hover Output:
**Entity Reference**
**Type**: User
**ID**: alice
**Status**: ✓ Found (line 15)
**Schema**: [id, name, email, created_at]
**Fields**: alice, Alice Smith, alice@example.com, 2024-01-15
If reference is unresolved:
**Entity Reference**
**Type**: User
**ID**: unknown_user
**Status**: ⚠ Not found
The referenced entity does not exist in this document.
endpoint: $api_url
^^^^^^^^
Hover Output:
**Alias Expansion**
**Name**: api_url
**Value**: https://api.example.com
**Defined at**: line 3
users: @User[id, name]
^^^^^
Hover Output:
**Type: User**
**Schema**: [id, name, email, created_at]
**Entities**: 125
**Nested Children**: Post (via %NEST)
| alice, Alice, ^
^
Hover Output:
**Ditto Operator (`^`)**
Repeats the value from the same column in the previous row.
**Example**:
| order1, @User:alice, pending
| order2, ^, shipped # Repeats @User:alice
Trigger: Ctrl+Click (or Cmd+Click) on references
Jump to exact definition location with precise character ranges:
# Line 15: Entity definition
users: @User[id, name]
| alice, Alice Smith
| bob, Bob Jones
# Line 42: Reference usage
author: @User:alice
^^^^^^^^^^^ # Ctrl+Click here
Result: Jumps to line 16, column 4 (the alice definition in matrix row)
Features:
@Type:id) and unqualified (@id) referencesTrigger: Right-click → Find All References
Find all locations where an entity or type is referenced:
# Definition at line 16
users: @User[id, name]
| alice, Alice Smith
^^^^^ # Find References here
# Shows all usages:
Result:
Found 5 references to 'alice':
- Line 16: Entity definition (users matrix)
- Line 42: author: @User:alice
- Line 58: reviewer: @User:alice
- Line 73: owner: @User:alice
- Line 91: created_by: @User:alice
Options:
include_declaration: Include the definition location in results (default: true)Performance: O(1) lookup via ReferenceIndex v2
Trigger: Ctrl+Shift+O (or Cmd+Shift+O) - "Go to Symbol in File"
Hierarchical outline of document structure:
📄 Document
├─ 📦 Header
│ ├─ 🏗 Schemas (3)
│ │ ├─ User [id, name, email, created_at]
│ │ ├─ Post [id, title, content, author]
│ │ └─ Comment [id, text, post, author]
│ ├─ 🔗 Aliases (2)
│ │ ├─ $api_url
│ │ └─ $version
│ └─ 🌳 Nests (1)
│ └─ Post > Comment
└─ 📊 Data
├─ 👥 users: @User (125 entities)
│ ├─ alice
│ ├─ bob
│ └─ carol
├─ 📝 posts: @Post (48 entities)
└─ 💬 comments: @Comment (312 entities)
Symbol Types:
$ prefix)Trigger: Ctrl+T (or Cmd+T) - "Go to Symbol in Workspace"
Query: Type to filter symbols across all open documents
Query: "user"
Results:
User (schema) in config.hedl:5
users (list) in data.hedl:12
user_roles (alias) in settings.hedl:8
user_count (field) in stats.hedl:23
Features:
Token Types:
%VERSION, %STRUCT, %ALIAS, %NEST@User, @Post)$api_url, $version)@, $, |, ^, ~Modifiers:
Trigger: Shift+Alt+F (or Shift+Option+F) - "Format Document"
Formats HEDL to canonical form using hedl-c14n:
Before:
users:@User[id,name]
|alice,Alice Smith
|bob,Bob Jones
After:
users: @User[id, name]
| alice, Alice Smith
| bob, Bob Jones
Features:
Trigger: F2 (or editor's rename command) on a symbol
Safe, validated rename refactoring for HEDL symbols with conflict detection and cross-document support:
Supported Symbol Types:
alice → alice_smith)User → Account)api_url → api_endpoint)email → email_address)Features:
Example:
# Rename entity 'alice' to 'alice_smith'
users: @User[id, name]
| alice, Alice Smith # Definition
| bob, Bob Jones
# References to alice (all updated):
owner: @User:alice # Line 1
author: @User:alice # Line 2
created_by: @User:alice # Line 3
Result: All 3 occurrences renamed to alice_smith in a single atomic operation.
Batches keystrokes together to prevent excessive parsing:
User types: "users: @User[id, name]"
Without debouncing:
- Parse after "u" (1st keystroke)
- Parse after "us" (2nd keystroke)
- Parse after "use" (3rd keystroke)
... 25 parses total (one per character)
With 200ms debouncing:
- Wait 200ms after last keystroke
- Parse once: "users: @User[id, name]"
... 1 parse total (~90% reduction)
Impact: Prevents stuttering during rapid typing, reduces CPU usage by ~90% during editing.
Content hash-based change detection prevents redundant parsing:
// Only parse if content actually changed
if content_hash != previous_hash {
parse_and_analyze(content);
}
Impact: Eliminates parsing when moving cursor without changes, saves ~30% of parse operations.
Parsed AnalyzedDocument cached in DocumentManager:
// Arc-wrapped for efficient concurrent access
document_cache: DashMap<Url, Arc<AnalyzedDocument>>
Impact: O(1) document retrieval for hover, completion, definition lookups.
Old Implementation: O(n) linear search through all entities New Implementation: HashMap-based O(1) lookups
pub struct ReferenceIndex {
// O(1) definition lookup
definitions: HashMap<(String, String), RefLocation>,
// O(1) references lookup
references: HashMap<String, Vec<RefLocation>>,
// Position-based reverse lookup
location_to_ref: HashMap<u32, Vec<(String, RefLocation)>>,
}
Impact: Go to Definition and Find References are instant even with thousands of entities.
Caches header end line to eliminate O(n) scans on every completion:
// Single-pass header parsing, cached result
header_end_line: Option<usize>
Impact: Context detection for completion is O(1) instead of O(n).
Per Document: 500 MB (configurable) Max Documents: 1000 with LRU eviction
pub struct DocumentManager {
documents: DashMap<Url, Arc<Mutex<DocumentState>>>,
cache_stats: Arc<Mutex<CacheStatistics>>,
max_cache_size: Arc<parking_lot::RwLock<usize>>, // 1000
max_document_size: Arc<parking_lot::RwLock<usize>>, // 500 MB
}
Enforcement: Checked on did_open and did_change events
Error Message (when exceeded):
Document size (520 MB) exceeds limit (500 MB)
When cache is full (1000 documents), least-recently-used documents are evicted:
pub struct CacheStatistics {
pub hits: u64,
pub misses: u64,
pub evictions: u64,
pub current_size: usize,
pub max_size: usize,
}
Cache Stats (available via statistics() method):
CacheStatistics {
hits: 15234,
misses: 1523,
evictions: 23,
current_size: 847,
max_size: 1000
}
All string slicing is UTF-8 boundary-aware:
// Safe slicing functions
pub fn safe_slice_to(s: &str, to: usize) -> &str
pub fn safe_slice_from(s: &str, from: usize) -> &str
Prevents: Panics on multi-byte character boundaries (emoji, non-ASCII)
Sync Kind: Full document sync
Events Handled:
textDocument/didOpen - Initial document analysistextDocument/didChange - Re-analysis after editstextDocument/didSave - Includes full text for immediate re-analysistextDocument/didClose - Cache cleanupSave Behavior: Includes text content in save notification for immediate re-analysis (ensures diagnostics update instantly).
On LSP initialize:
{
"capabilities": {
"textDocumentSync": {
"openClose": true,
"change": 1,
"save": { "includeText": true }
},
"completionProvider": {
"resolveProvider": false,
"triggerCharacters": ["@", ":", "%", "$", "|"]
},
"hoverProvider": true,
"definitionProvider": true,
"referencesProvider": true,
"documentSymbolProvider": true,
"workspaceSymbolProvider": true,
"documentFormattingProvider": true,
"renameProvider": {
"prepareProvider": true
},
"semanticTokensProvider": {
"legend": {
"tokenTypes": ["keyword", "type", "variable", "string", "number", "comment", "operator"],
"tokenModifiers": ["definition", "declaration"]
},
"range": false,
"full": true
}
}
}
Code Lens: Not implemented—no actionable commands displayed inline.
Code Actions: No quick-fixes or refactoring actions (e.g., "Add schema declaration").
Folding Ranges: No code folding support for matrix lists or nested structures.
Call Hierarchy: Not applicable to HEDL's declarative data model.
Linked Editing Range: Simultaneous editing of related symbols not supported.
Inlay Hints: No inline type hints or parameter names displayed.
AnalyzedDocument:
pub struct AnalyzedDocument {
document: Option<Document>, // Parsed AST
errors: Vec<HedlError>, // Parse errors
lint_diagnostics: Vec<Diagnostic>, // Lint warnings
entities: HashMap<String, HashMap<String, usize>>, // type -> id -> line
schemas: HashMap<String, (Vec<String>, usize)>, // type -> (cols, line)
aliases: HashMap<String, (String, usize)>, // name -> (value, line)
reference_index_v2: ReferenceIndex, // O(1) reference lookup
nests: HashMap<String, (String, usize)>, // parent -> (child, line)
header_end_line: Option<usize>, // Cached header boundary
}
ReferenceIndex (v2):
pub struct ReferenceIndex {
// Definition locations: (type, id) -> location
definitions: HashMap<(String, String), RefLocation>,
// Reference locations: reference_string -> vec of locations
references: HashMap<String, Vec<RefLocation>>,
// Reverse mapping: location -> reference string
location_to_ref: HashMap<u32, Vec<(String, RefLocation)>>,
}
Comprehensive test suite in tests.rs:
HEDL Schema Development: Write HEDL schemas with instant validation feedback, autocomplete for types and references, go-to-definition for entity lookups.
Configuration File Editing: Edit HEDL configuration files with real-time error checking, hover documentation for directives, format on save for consistency.
Large Document Navigation: Navigate large HEDL files with document symbols outline, workspace-wide entity search, find all references to entities.
Multi-File Projects: Work across multiple HEDL files with workspace symbols search, cross-document reference validation, consistent formatting.
Learning HEDL: Explore HEDL syntax with hover documentation on directives, context-aware completions showing available options, inline diagnostics explaining errors.
Parse Performance: ~100-200 MB/s for typical HEDL files
Debouncing: ~90% reduction in parse operations during typing
Reference Lookups: O(1) with ReferenceIndex v2 (previously O(n))
Completion: O(1) context detection with cached header end line
Memory: O(document_size + cache_size), with LRU eviction at 1000 documents
Responsiveness: 200ms debounce ensures smooth typing experience without stuttering
Detailed performance benchmarks are available in the HEDL repository benchmark suite.
tower-lsp 0.20 - LSP protocol implementationtokio 1.0 - Async runtime (features: full)dashmap 5.5 - Concurrent HashMapparking_lot 0.12 - High-performance mutexesropey 1.6 - Rope for efficient document editingtracing 0.1 - Structured loggingtracing-subscriber 0.3 - Logging configuration (features: env-filter)hedl-core (workspace) - HEDL parserhedl (workspace) - HEDL core libraryhedl-lint (workspace) - HEDL linterhedl-c14n (workspace) - HEDL canonicalizationserde (workspace) - Serialization frameworkserde_json (workspace) - JSON supportthiserror (workspace) - Error handlingApache-2.0