org-lsp

Org in Vim

The Vim plugin (editors/vim) runs Org through the server on top of an LSP client: yegappan/lsp, coc.nvim, vim-lsp or ALE. It needs Vim 9.0.1485 or newer.

The client starts the server and keeps it up to date with the buffers; the plugin does the rest the same way for all four: it runs the server's commands with the cursor position and asks what they ask for, shows the agenda and capture buffers, highlights with the server's tokens (as text properties), folds with its folding ranges, and shows the clock.

Which client#

From the one that works best with org-lsp to the one that works least well (client = 'auto' takes them in this order):

yegappan/lsp
Everything works, with the least glue: the plugin talks to the server on the client's own channel.
coc.nvim
Everything works, as with yegappan/lsp: the server tells the plugin of the documents to show, rather than coc.nvim, so they open as win_split_mode says. One difference is left: a file an edit is for that is not loaded opens as workspace.openResourceCommand says (see What differs from Neovim). It needs Node.js.
vim-lsp
Everything works too, but vim-lsp does not ask for the inlay hints (the time clocked, the … of shrunk columns) again when the server says they changed: the plugin asks for them after each command, and vim-lsp when the cursor moves.
ALE
Works, but it has no inlay hints (the time clocked, the … of shrunk columns), does not apply the server's edits itself (the plugin does), and one server serves the whole session, started for the directory of the first Org file.

Installing#

With vim-plug:

Plug 'yegappan/lsp'   " or coc.nvim, vim-lsp, ALE: see Which client
Plug 'kristijanhusak/org-lsp', { 'rtp': 'editors/vim', 'do': ':OrgLspDownload' }

With Vim's own packages, clone the repository and add editors/vim to 'runtimepath'.

Opening an Org file the first time downloads the server for your platform from the nightly release (with curl and tar; on Windows, Windows' own tar, not one Git put first on the PATH), into ~/.local/share/vim/org-lsp, and starts it. :OrgLspDownload downloads it again, to update it, and the do above does that on each update of the plugin; a server that runs already is replaced when Vim starts again. 'release': 'v0.2.0' in g:org_lsp picks a release instead of the nightly build, and 'download': 0 turns the download off. A release that needs a GitHub login, as one of a private repository, is downloaded with the GitHub CLI (gh) when it is installed and logged in.

To build the server instead (with cargo), set 'do': 'cargo build --release -p org-lsp'. The plugin takes the server cmd names, else a build in its checkout (target/release), org-lsp on PATH, or the downloaded one. :Org version says which server runs, with the commit it was built from (org-lsp version 0.1.0-f7e7b68; -dev after it for a server built by hand, not by the release workflow).

The plugin finds the client that is installed and registers the server with it. Leave the client's own semantic highlighting and folding off for Org files (they are off by default in all four): the plugin does both.

The plugin's syntax for Org (syntax/org.vim) colors what a pattern can tell, as the server does and in the same colors: headline stars and titles by level, timestamps, planning and clock lines, links, emphasis, code, lists, tables, drawers, blocks, keywords and comments. TODO keywords and the headlines they start, keywords and tags with faces of their own (org_todo_keyword_faces, org_tag_faces) and the options of how headlines look it colors as the server tells it for each file; before it has, from the file's #+TODO: lines. It shows what you type before the server has answered. What only the server can tell (priorities, entities, LaTeX, what other options change) waits for it. It needs :syntax on.

To try it without touching your own configuration, editors/vim/minimal.vim is a vimrc that puts vim-plug and yegappan/lsp in a temporary folder and loads the plugin from a checkout (~/github/org-lsp; change both places it is named for another):

cargo build --release -p org-lsp
vim -N -u editors/vim/minimal.vim ~/org/todo.org

Setting it up#

Set g:org_lsp before the plugin loads. The server's options go in settings, the plugin's own beside it; every key may be left out:

let g:org_lsp = {
      \ 'settings': {
      \   'org_agenda_files': ['~/org'],
      \   'org_todo_keywords': ['TODO(t)', 'NEXT(n)', '|', 'DONE(d!)'],
      \   'org_capture_templates': [
      \     {'key': 't', 'description': 'Task', 'target': ['file', '~/org/inbox.org'], 'template': "* TODO %?\n  %U"},
      \   ],
      \   'org_agenda_custom_commands': [
      \     {'key': 'n', 'description': 'Next actions', 'type': 'todo', 'match': 'NEXT'},
      \   ],
      \ },
      \ 'client': 'lsp',
      \ 'win_split_mode': 'vsplit',
      \ 'mappings': {'org': {'org.todo.next': '<Leader>t'}},
      \ }

The server also reads the shared configuration file, ~/.config/org-lsp/config.toml, which every editor shares; settings goes on top of it. configuration.org has everything else: the plugin's options (the client, windows, the calendar, the statusline) and every option of the server with its default.

Keys#

The keys are the Neovim plugin's (cit, <C-a>, <Leader>oa, <Tab> …), and the agenda's are the ones it has in every editor. g? lists the keys of the buffer at hand, K in the agenda shows the entry of the line in a popup, and :Org agenda, :Org capture, :Org todo next … run any command. keys/vim.org lists every key and how to change it.

What differs from Neovim#

  • The agenda and the date prompt's calendar are files the server writes (Agenda.org-agenda, Date.org-date in a temporary directory), read again when it says they changed.
  • Column view covers each headline with a popup, and puts its header in the window's status line rather than above the text.
  • What stands in for hidden text (a bullet, an entity's character, a custom time) is text of its own before the hidden text, and a line with a long link wraps as if the hidden target took room: Vim counts concealed text when it wraps.
  • With coc.nvim, a file an edit is for that is not loaded (the target of a refile or a capture) opens as workspace.openResourceCommand says, in a tab of its own unless that is set ("" loads it without showing it, as the other clients do). coc.nvim then says Use ':wa' to save changes …; with the command's message, that is more than a narrow command line holds, and Vim waits for Enter ("coc.preferences.messageLevel": "warning" keeps coc.nvim's to itself).
  • With ALE, one server serves the session, started for the directory of the first Org file. ALE shows the hover where the cursor rests in the command line (g:ale_hover_cursor): in the agenda, the entry's headline, which takes the place of the command's message after 'updatetime'.

What works#

FeatureParityVimWhat does not work, and why
Highlighting100%❓A headline's line colored to the edge (org_fontify_whole_heading_line) is a sign without text, which shows the sign column. A line with a long link wraps as if the link's hidden target took room: Vim counts concealed text when it wraps.
Folding and visibility91%❓Cycling the whole file goes between overview and everything: it has no contents state, which needs folds of a headline's own text that Vim's fold levels do not make. A closed fold's line is not highlighted: Vim draws it in one color. <Tab> changes the level of a new entry in Normal mode; in Insert mode it is a tab.
Headings and structure96%✅
Lists and checkboxes93%✅
TODO keywords100%✅
Priorities100%✅
Tags88%✅
Properties and column view100%❓Column view's header is in the window's status line, shown when the window has one (laststatus). Custom properties are not hidden (org.custom.properties.toggle): Vim cannot hide a line, a closed fold still shows a line of its own, and concealing a line's text leaves the line, empty.
Dates and times100%✅
Agenda97%✅
Clocking100%✅
Capture100%✅
org-protocol86%✅
Notes (org-roam)94%✅
A journal (org-journal)93%✅
Refile and archive100%✅
Links96%✅
Footnotes100%✅
Tables100%✅
Table formulas100%✅
Code blocks (Babel)100%✅
Export and publishing100%✅
Citations82%✅
Diagnostics (org-lint)100%✅
Indentation and formatting100%❓The lines a long line wraps into line up with its own indentation, a headline's title or an item's text ('breakindent'), without the prefix of virtual indentation before them: Vim repeats no virtual text on them, and 'breakindentopt' shifts all the lines of a window alike.
Notes and logging100%✅
Completion and editing helpers100%✅
Timers100%✅
Attachments100%✅
Encryption100%✅
Inline images and LaTeX previews100%❌Vim cannot show images.
All of Org97%

✅ 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#

editors/vim/test/run.sh runs the plugin against the real server once per client, in Vim inside tmux. CLIENTS is a directory with the clients checked out (lsp, vim-lsp, coc.nvim from its release branch, ale):

CLIENTS=~/vim-clients editors/vim/test/run.sh           # all four
CLIENTS=~/vim-clients editors/vim/test/run.sh vim_lsp   # one

After the plugin's own tests it runs the shared end-to-end scenarios, pressing the plugin's keys for each step (development.org).