Elot: Org-mode Ontology Editor for VS CodeLiterate ontology engineering in Visual Studio Code. Write OWL ontologies as plain-text Org-mode files — with label display, IntelliSense, syntax checking, folding, and one-click OWL export. ELOT (Emacs Literate Ontology Tool) is an established workflow for ontology engineering in which a single Org-mode document is simultaneously your ontology source, its documentation, and your analytical workspace. This extension brings the core ELOT experience to VS Code — no Emacs required. Features at a Glance
Getting Started
Importing existing OWL ontologiesHave an existing ontology in RDF/XML, Turtle, or another OWL format? The extension can convert it to ELOT's Org-mode format directly from VS Code — no command line needed.
Java runtime requirementThe OWL importer runs
If the major version is below 21, running the importer will fail with
Install a current JDK from one of:
On managed machines where the system Java cannot be replaced, install a
newer JDK alongside it and point the extension at it via the
The extension defaults You can also run the exporter from the command line:
Download it from the releases page. Label Display (CURIE → Human-readable Labels)Ontology files are full of identifiers like Hover InformationHover over any CURIE (e.g.
Always on by default. Disable via the setting Visual Label Replacement (Toggle)Press F5 (or click the 🏷 icon in the editor title bar, or the status bar indicator, or right-click → Elot: Toggle Label Display) to visually replace CURIEs with their labels throughout the editor. When enabled:
Toggle methods:
Headline FoldingOrg headings can be folded and unfolded, just like in Emacs Org-mode. Each heading's fold region extends to just before the next sibling or ancestor heading.
The Tab and Shift+Tab bindings are scoped to
Go to Definition (Jump to Entity Heading)When you see a CURIE like
Works purely within the current Org file — no external index or language server required. Org Indent Mode (Visual Indentation)Toggle visual indentation that mimics Emacs's
IntelliSense: Insert Existing ResourcePress Ctrl+Space to get a dropdown of all OWL entities declared in the current Org file — searchable by label or CURIE.
Selecting an item inserts the CURIE into the document. You can also
auto-trigger by typing OWL Axiom Syntax CheckingThe extension validates OWL Manchester Syntax axioms as you type. Any
description list item whose tag is an OMN keyword ( If the axiom value is malformed, a red squiggly underline appears with an error message. Diagnostics are updated on every save and when the file is opened.
Example:
Errors are caught immediately — no need to load the ontology into a reasoner first. FontificationTwo always-on decorations enhance readability:
Applied automatically — no toggle or configuration needed. Settings
DB-backed (global) label display has its own settings — see
Label Database below
( Change in VS Code Settings (search for "elot"). Tip: Word Wrap for Org FilesOrg files often have long lines. Press Alt+Z to toggle word wrap, or add
this to your
Label Database (
|
| Setting | Default | Description |
|---|---|---|
elot.dbPath |
"" |
Absolute path to the SQLite DB. Empty = use the per-platform VS Code globalStorage location. Workspace-level overrides user-level. |
elot.activeLabelSources |
[] |
Ordered list of active sources. Each entry is either a string (source name) or {"source": "name", "dataSource": "path"}. First match wins on lookup conflicts. |
elot.preferredLanguages |
[] |
Preferred language tags (BCP-47), in priority order. Empty = ["", "en"] (untagged first, then English). |
elot.globalLabelDisplay.hoverEnabled |
true |
Show DB-backed hovers in non-Org files. |
elot.globalLabelDisplay.maxIds |
500 |
Per-file cap on how many CURIE/IRI tokens are decorated. Status bar shows a "capped" warning when reached. |
elot.globalLabelDisplay.includeLanguages |
[] |
VS Code language IDs in which DB-backed hover / decoration apply. Empty = built-in default (plaintext, markdown, typescript, javascript, python, json, yaml, xml, turtle, sparql, …). Org is always excluded. |
settings.json accepts both the shorthand and the canonical form for
active sources:
{
// Shorthand:
"elot.activeLabelSources": ["bfo", "ro"],
// Canonical (also written by the management commands):
"elot.activeLabelSources": [
{ "source": "bfo", "dataSource": "" },
{ "source": "ro", "dataSource": "" }
],
"elot.preferredLanguages": ["en", ""]
}
Beware JSONC commas. VS Code's
settings.jsonparser is forgiving but a trailing comma after the last key/value can silently invalidate a setting. If a label-related setting "doesn't seem to take effect", open the file and look for a stray comma before the closing brace.
Commands (Command Palette)
| Command | Effect |
|---|---|
Elot: Toggle Global Label Display (F5 in non-Org files) |
Show/hide DB-backed labels in the current editor. |
Elot: Activate Label Source |
Multi-select QuickPick of inactive sources; appends to elot.activeLabelSources. |
Elot: Deactivate Label Source |
Multi-select QuickPick of active sources; removes from elot.activeLabelSources. |
Elot: Reorder Active Label Sources |
Two-step QuickPick: pick a source, then move up / down / top / bottom / done. (Per-item button reorder lands in 2.3.6.) |
Elot: Label DB Info |
Diagnostic: shows the resolved DB path, bridge state, registered sources with id counts, and current settings. Useful as a first stop when something looks off. |
The settings round-trip preserves the original scope of the key (workspace
vs. user) and always writes the canonical {source, dataSource} shape so
the resulting settings.json is unambiguous after a UI edit.
Status bar
Sits in the bottom-right, next to the Org-side label-display indicator. Format:
🏷 Labels (3 src, 12k ids) ← display ON
</> CURIEs (3 src, 12k ids) ← display OFF
🏷 Labels (3/5 src, 12k ids, capped) ← scan cap reached
$(database) ELOT: no DB ← bridge has no file open
When active source count differs from total registered sources, the
format collapses to (N/M src, K ids). Hover the indicator for a full
breakdown (path, per-source id counts, prefs).
Database location
By precedence (extension):
- Workspace-level
elot.dbPath - User-level
elot.dbPath $ELOT_DB_PATHenvironment variablecontext.globalStorageUri.fsPath+/elot.sqlite:- Windows:
%APPDATA%\Code\User\globalStorage\johanwk.elot\elot.sqlite - macOS:
~/Library/Application Support/Code/User/globalStorage/johanwk.elot/elot.sqlite - Linux:
~/.config/Code/User/globalStorage/johanwk.elot/elot.sqlite
- Windows:
The CLI uses the same logical defaults (its --db flag wins, then
$ELOT_DB_PATH, then the same per-platform globalStorage location,
then ~/.elot/elot.sqlite as a final fallback). When the publisher id
matches, the extension and the CLI converge on the same file.
The VS Code extension and Emacs each own their own DB by default — they do
not share state. Use --db or $ELOT_DB_PATH if you want a shared file.
Commands
# Create / open the DB (idempotent; creates parent dirs)
elot-cli db init [--db <path>]
# Register a source (replaces any existing rows for the source name)
elot-cli db register <file> --source <name> [--type csv|tsv|json|ttl|rq|org]
[--data-source <path-or-url>]
elot-cli db refresh <name> --file <file> [--type ...]
# Inspect
elot-cli db list [--prefixes] [--format tsv|json|table]
elot-cli db lookup <label> [--active] [--format tsv|json]
elot-cli db attr <id> [<prop>] [--format tsv|json]
# Remove a source (cascades to its entities, attributes, prefixes)
elot-cli db remove <name>
<id> accepts a literal stored id, a CURIE (expanded against the DB's
prefix table), or a full URI (contracted against the same table). All
read commands honour $ELOT_PREFERRED_LANGUAGES (BCP-47 tags,
comma-separated; the empty tag means "untagged").
Source types
| Type | Parser notes |
|---|---|
csv |
RFC-4180. First row is the header. Required column: id. Optional label and lang. Other columns become attributes. Header label@en is shorthand for a tagged label. |
tsv |
Same as CSV with tab delimiter. |
json |
Two shapes: flat [{id, label, lang?, ...attrs}, ...] and nested {prefixes?: {pfx: iri}, entities: [{id, label?, kind?, attrs?: {prop: value\|[v..]\|{lang: v}}}]}. |
ttl |
Runs ROBOT ($ELOT_ROBOT_JAR or robot on PATH) with a default SELECT ?id ?label (LANG(?label) AS ?lang) query. Override per-project: place <root>/.elot/ttl-label-query.rq (where <root> is the nearest ancestor with .git, .elot, or .elot-cache). Prefixes are harvested from the file's @prefix lines. |
rq |
Executes a SPARQL SELECT query against --data-source <ttl-file-or-http-endpoint> via ROBOT. Results are cached in <root>/.elot-cache/<query>.<sha1>.csv; an empty result preserves any existing cache (defensive). Prefixes are harvested from the query's PREFIX lines. |
org |
Reuses the orgize WASM parser + ELOT slurp builder. Supports the standard :prefixdefs: block, ID-suffix CURIE conventions, and language-tagged headings. |
Type is auto-detected from the file extension if --type is omitted.
Worked example
# Initialise an empty DB
elot-cli db init --db /tmp/elot.sqlite
# Ingest a CSV with English and Korean labels
cat > /tmp/labels.csv <<'EOF'
id,label,lang
ex:widget,Widget,en
ex:widget,위젯,ko
ex:widget,Dings,de
EOF
elot-cli db register /tmp/labels.csv --db /tmp/elot.sqlite --source demo
# Lookup
elot-cli db lookup Widget --db /tmp/elot.sqlite # ex:widget
elot-cli db attr ex:widget rdfs:label --db /tmp/elot.sqlite # Widget (en wins)
# Korean prefs
ELOT_PREFERRED_LANGUAGES=ko \
elot-cli db attr ex:widget rdfs:label --db /tmp/elot.sqlite # 위젯
# List in JSON
elot-cli db list --db /tmp/elot.sqlite --format json
Schema
Schema version 3 only. Older databases are refused with a clear message
directing the user to upgrade via Emacs. The canonical DDL is shipped as
elot-package/schema.sql and consumed verbatim by both the Elisp writer
and the TS writer; a byte-identical golden round-trip test
(test/fixtures/golden/) keeps the two implementations in lockstep.
About ELOT
ELOT introduces literate ontology engineering: a workflow where a single Org-mode plain-text file is the authoritative source for an OWL ontology and its documentation. Headlines are the taxonomy; description lists are the axioms and annotations.
This approach has been used across scores of ontology projects, including the
ISO 23726-3 Industrial Data Ontology. See the
ELOT repository for the full project,
including the Emacs package, examples, and the elot-exporter tool.
Org→OWL Pipeline
The extension includes a complete Org→OWL pipeline built on orgize (a Rust Org-mode parser compiled to WebAssembly). It supports:
- Heading-to-entity mapping (URIs, CURIEs, and labels from Org headings)
- Property drawer and keyword extraction
- Description list → OWL annotation/restriction conversion
- Prefix table management
- Ontology, Class, ObjectProperty, DataProperty, AnnotationProperty, and Individual frames
- Meta-annotations via nested description lists
- Automatic tangle to the file specified in
:header-args:omn: :tangle
Developer Documentation
The sections below are for contributors who want to build the extension from source.
Prerequisites
Setup (after cloning)
cd tools/elot-cli
npm install
Building
# Full build (WASM + esbuild bundle):
npm run bundle
This runs two stages:
npm run build:wasm— runswasm-pack build --target nodejsinsideelot-orgize/, producingsrc/wasm/elot_orgize.jsandsrc/wasm/elot_orgize_bg.wasm.node esbuild.mjs— cleansdist/, copies the.wasmfile, then bundlessrc/extension.ts→dist/extension.jsandsrc/cli.ts→dist/cli.jsvia esbuild.
Generated files in src/wasm/ and dist/ are gitignored — run
npm run bundle after every fresh clone.
CLI Usage
# Generate OMN, write to stdout
node dist/cli.js examples/bfo-core.org -
# Write to a specific file
node dist/cli.js examples/bfo-core.org output.omn
# Uses the :tangle target from the Org file, or stdout
node dist/cli.js examples/bfo-core.org
# Export to styled HTML (requires Pandoc on PATH)
node dist/cli.js --html examples/bfo-core.org
# Export to HTML with explicit output path
node dist/cli.js --html examples/bfo-core.org output.html
The --html flag exports to styled HTML via Pandoc instead of generating
Manchester Syntax. It applies the same CURIE linkification, label resolution,
and ELOT CSS/JS theming as the VS Code "Export to HTML" command. If no output
path is given, the .org extension is replaced with .html.
VS Code Extension Packaging
npx @vscode/vsce package
Produces elot-<version>.vsix containing dist/extension.js, dist/cli.js,
dist/elot_orgize_bg.wasm (~284 KB), package.json, LICENSE, and this
README.
Testing
npm test # Golden-file tests
npx tsx src/tests/entityFromHeader.test.ts # Unit tests
npx tsx src/tests/annotationValue.test.ts
npx tsx src/tests/omnKeywords.test.ts
Rebuilding After Changes
| What changed | What to run |
|---|---|
Rust code in elot-orgize/src/ |
make bundle (rebuilds WASM + re-bundles) |
TypeScript in src/ |
make bundle (re-bundles) |
| Only type-checking | make build |
Fresh .vsix |
make package (runs bundle via prepublish) |
| Forgot the commands | make help |
Architecture
Org text → [orgize WASM] → JSON AST → parseOrg() → ElotNode tree → generateOmn() → OMN
parseOrgWasm.ts— Calls orgize WASM, maps JSON toElotNodetreecli.ts— CLI entry pointextension.ts— VS Code extension entry pointgenerateOmn.ts— Assembles OMN from node treeomnDeclarations.ts— Generates frames for each nodeomnFrame.ts— Formats individual OWL frames
The Rust crate elot-orgize/ contains lib.rs (WASM entry), parse.rs
(AST walker), and types.rs (data structures).