Org in VS Code
The VS Code extension (editors/vscode) runs Org through the server. It is
a thin client: it starts the server and tells it where the cursor is when a
command runs. Its commands, keys and token colors are written into
package.json from the server's own description of itself, so nothing
about Org is listed there by hand. It works in VS Code's relatives too
(VSCodium, Cursor).
Installing
curl -fsSL https://github.com/kristijanhusak/org-lsp/releases/download/nightly/install.sh | bash -s -- vscode
or, in a checkout of this repository:
scripts/install.sh vscode # the nightly build for this machine
scripts/install.sh --release v0.2.0 vscode # a release
scripts/install.sh --source vscode # built from this checkout (cargo, npm)
The script downloads the package for your platform from the nightly release
(or the release named), or builds it, and installs it with
code --install-extension. Run it again to update, then reload the window.
CODE=codium (or code-insiders, cursor) installs into another editor.
On Windows without a shell, download org-lsp-win32-x64.vsix from the
release and run Extensions: Install from VSIX….
The package has the server inside. The extension looks for the server in
this order: the org-lsp.server.path setting, the one inside the
extension, org-lsp on PATH.
Setting it up
The settings go in settings.json (Preferences: Open User Settings
(JSON)). The server's options go in org-lsp.settings, where they are
completed, described on hover and checked; the extension's own settings
beside it:
{
"org-lsp.settings": {
"org_agenda_files": ["~/org"],
"org_todo_keywords": ["TODO(t)", "NEXT(n)", "|", "DONE(d!)"],
"org_log_done": "time",
"org_capture_templates": [
{ "key": "t", "description": "Task", "target": ["file+headline", "~/org/inbox.org", "Tasks"], "template": "* TODO %?\n %U" }
],
"org_agenda_custom_commands": [
{
"key": "d",
"description": "Today, then what is waiting",
"blocks": [
{ "type": "agenda", "settings": { "org_agenda_span": "day" } },
{ "type": "todo", "match": "WAIT", "header": "Waiting for" }
]
}
]
},
"org-lsp.keys": "vim",
"org-lsp.winSplitMode": "vsplit"
}
Without org_agenda_files, the agenda is built from the Org files in the
workspace folders. When org-lsp.settings changes, the extension restarts
the server, so the change applies at once. A folder's
.vscode/settings.json may set them too, but only once VS Code trusts the
folder: in restricted mode, org-lsp.settings and org-lsp.server.path
come from the user's settings alone, as they name programs the server runs.
The server also reads the shared configuration file,
~/.config/org-lsp/config.toml, which every editor shares;
org-lsp.settings goes on top of it. configuration.org has everything else:
the extension's settings and every option of the server with its default.
Headline levels in colors
Color themes give every heading the same color, so by default each level
of headlines looks alike. With "org-lsp.headlineLevelColors": true each
level takes a color of its own from the theme, as Emacs's org-level-N
faces take theirs from outline-1 to outline-8: the colors of function
names, variables, keywords, comments, types, constants, built-ins and
strings, the stars with the title. The text past a done keyword
(org_fontify_done_headline) or a TODO one (org_fontify_todo_headline),
archived headlines and inline tasks keep their own look. The change shows
at once in the open files.
The headlines then have the token type outline with the modifiers
level1 to level8, so editor.semanticTokenColorCustomizations picks
other colors, for every theme or for one:
{
"org-lsp.headlineLevelColors": true,
"editor.semanticTokenColorCustomizations": {
"rules": {
"outline.level1:org": { "foreground": "#4f97d7", "bold": true },
"outline.level2:org": "#2d9574"
},
"[Default Light Modern]": {
"rules": { "outline.level1:org": "#3a81c3" }
}
}
}
Using it
The commands are in the Command Palette under Org, and the changes that
apply at the cursor under Quick Fix… (Ctrl+.). Org: Show the keys
lists the keys of the editor at hand; picking one runs it.
Emacs's keys are bound in Org files (Ctrl+C Ctrl+T, Ctrl+C Ctrl+S,
Shift and the arrows, Tab and Shift+Tab to cycle folds …), in capture
buffers (Ctrl+C Ctrl+C) and in the date prompt. In Org files Ctrl+C
waits for a second key instead of copying (Ctrl+Insert still copies). The
agenda has the keys it has in every editor: Emacs's agenda keys (f, b,
t, +, Enter, Ctrl+C Ctrl+S …), with Shift+J for a date and the views
under v (v d, v w), so that VSCodeVim keeps its motions; q closes it.
org-lsp.keys picks other keys: "vim" binds the Neovim plugin's keys for
VSCodeVim, "custom" none. keys/vscode.org lists every key and command,
and how to add, change and remove keys.
A date is typed as at Org's prompt: fri 10:00, +2d, 2/15, ++1w,
10:00-11:30. Under the input box the extension shows the timestamp the
answer makes, as you type. As in Emacs, a date command shows a calendar
first, beside the file, with the answer and what it means on its last line.
Emacs's keys work there: Shift and the arrows by day and week,
Alt+Shift+Left and Alt+Shift+Right by month, Alt+Shift+Up and
Alt+Shift+Down by year, < and > for other months, . for today, Enter
to take the day under the cursor, i to type the answer, q or Ctrl+G to
give up.
A change made from the agenda edits the entry's file and saves it
(auto_save; off, it is left unsaved, as in Emacs). As in Emacs, the
entry's lines stay where they are, showing the new state, priority or date,
with the cursor on them, until the agenda is built again with r.
What works
| Feature |
Parity |
VS Code |
What does not work, and why |
| Highlighting |
100% |
✅ |
|
| Folding and visibility |
91% |
❓ |
A closed fold ends in VS Code's own ⋯: an extension cannot change it (org_ellipsis). |
| Headings and structure |
96% |
❓ |
VS Code does not tell an extension which folds are closed: C-k on a folded headline is VS Code's own (org-ctrl-k-protect-subtree), and a subtree cut or copied folded is not folded again when it is pasted. |
| Lists and checkboxes |
93% |
✅ |
|
| TODO keywords |
100% |
✅ |
|
| Priorities |
100% |
✅ |
|
| Tags |
88% |
✅ |
|
| Properties and column view |
100% |
❓ |
Column view's header is in the status bar, with the column picked in brackets. Custom properties are not hidden (org.custom.properties.toggle): VS Code hides lines only in a fold of the folding ranges, which keeps its first line showing, and a decoration cannot hide a line. |
| Dates and times |
100% |
✅ |
|
| Agenda |
97% |
❓ |
Tags are at column 80, not at the window's edge (org_agenda_tags_column "auto"): VS Code does not tell an extension how many columns an editor shows. |
| Clocking |
100% |
✅ |
|
| Capture |
100% |
✅ |
|
| org-protocol |
86% |
✅ |
|
| Notes (org-roam) |
94% |
✅ |
|
| A journal (org-journal) |
93% |
✅ |
|
| Refile and archive |
100% |
✅ |
|
| Links |
96% |
✅ |
|
| Footnotes |
100% |
✅ |
|
| Tables |
100% |
✅ |
|
| Table formulas |
100% |
✅ |
|
| Code blocks (Babel) |
100% |
✅ |
|
| Export and publishing |
100% |
❓ |
VS Code does not tell an extension which folds are closed: with C-v everything is exported. |
| Citations |
82% |
✅ |
|
| Diagnostics (org-lint) |
100% |
✅ |
|
| Indentation and formatting |
100% |
❓ |
The lines a long line wraps into are not indented as Org does: VS Code indents them by the line's own leading whitespace (editor.wrappingIndent), the same way for every line, without the prefix of virtual indentation, a decoration it does not count. |
| Notes and logging |
100% |
✅ |
|
| Completion and editing helpers |
100% |
✅ |
|
| Timers |
100% |
✅ |
|
| Attachments |
100% |
✅ |
|
| Encryption |
100% |
✅ |
|
| Inline images and LaTeX previews |
100% |
❓ |
VS Code cannot leave lines free for an image: an image takes the place of its link only as high as a line, and shows whole on hover (at the width Org gives it, at most 600 pixels). Counts through a key's args. |
| All of Org |
97% |
|
|
✅ all that org-lsp does works there, ❓ some does not (the last column says what and why),
❌ none does, — nothing to support yet. Parity is the share of what
Emacs Org does in that area that org-lsp does; parity.org lists both.
TESTING.org lists what to try by hand.
Developing
scripts/install.sh --source vscode (in the repository root) builds the
extension and installs it. By hand, in editors/vscode:
cargo build --release -p org-lsp # in the repository root
npm install
npm run generate # package.json from the server's manifest
npm run bundle-server # copies the binary into server/
npm run package # writes org-lsp-<version>.vsix
Packaging writes README.md, which VS Code shows on the extension's page and
reads only as Markdown, from this page (scripts/readme.mjs, run by vsce as
vscode:prepublish); it is not kept in the repository.
npm test runs the extension inside VS Code against the server
(xvfb-run -a npm test on a machine without a display; VSCODE_EXECUTABLE
names a VS Code or VSCodium to use instead of downloading one).