LSP: TOC - #86
Merged
Merged
Conversation
hon-gyu
commented
Aug 4, 2026
Owner
- lsp: let a daily-note action open the note it creates
- lsp/toc: table of contents, and take anchors from the parser
- lsp: an attribute anchor is where its {#id} is written
A creating code action carried only the command, and the command reached the note through window/showDocument. Clients that do not implement the request — Zed, for one — got a file on disk, no buffer, and a message saying so. Carry the WorkspaceEdit on the action instead: a CreateFile plus a text edit against the new note that writes the empty string. A client cannot apply a text edit to a document without opening it, so creation now reaches a document by the better-supported half, and the note is as empty as it always was. The command that follows is asked to open only: its own CreateFile would race the buffer the client just opened, which may be unsaved and so absent from disk. A third, optional argument tells it an edit already opened the note, so a failure to focus goes unreported rather than contradicting what the reader is looking at. A lens has no edit to carry — a CodeLens is a command and nothing else — and keeps the old behaviour. Opening a note that already exists is unchanged: there is no edit to make for a file that is already as it should be, and the link action remains the route there.
Two changes, in one commit because they touch the same module list and
the second grew out of the first.
TOC
---
A table of contents lives in a `::: toc` div — the vault's own fenced-div
extension, not marker comments. A div's body is ordinary block content, so
the list parses as a list and its links resolve like any other; a raw
`<div>` would make that depend on blank lines, and a comment renders to
nothing a stylesheet can reach. The cost, stated in the spec: it is visible
in source and output, where a comment is invisible in both.
- `Insert table of contents` (Refactor) outside a region;
- `table of contents is out of date` on the opening fence, with
`Update table of contents` (QuickFix) rewriting the body between the
fences and never the fences themselves;
- both act on the buffer rather than disk: a TOC describes headings that
were just typed, so an edit derived from disk would splice a different
version of the file into this one.
Regions are found by folding for `Cmarkit.Block.Ext_div`, which needs
`~layout:true` — without it the div is still a div but its fences carry no
location. `Lsp_util.parse_doc` gains `?layout` for that.
Anchors
-------
Recognizing fences by reading lines was drift waiting to happen, and the
same pattern was already in four other places, all rooted in
`Hover.heading_level_of_line`: count the `#`s, slugify what follows, look
for a trailing `^id`, match a `{#id}` line.
`Anchors` replaces them with one parser-based answer, built on the
extractors the vault index already uses. It fixes two live bugs:
- a `#` or `^id` inside a fenced code block was an anchor, to hover,
find-references, rename and the reference lens;
- heading identity was re-derived with `heading_id_of_text`, so a heading
carrying an authored `{#id}` — or a parser-deduplicated `same-1` — could
not be found at all. `test_hover_definition_agreement` was written to
catch exactly this drift; it is now structural.
find-references gains cursor-on-inline-`{#id}`, previously a documented
limitation. One reconstruction remains and is documented where it lives:
the AST does not locate a block-attribute line (`Ext_attributes` spans the
block it attributes, not the `{#id}` above it), so `spec_line_above` finds
it — handing the candidate to the parser's own attribute reader rather than
matching a shape.
The principle is now written down in `feature-index.mld`, with completion
named as the one exception: it runs on text that is not yet syntax.
Takes the oymarkit fix (submodule 027bb7c, "parser: locate block attribute
specifiers"), which makes `Block.Ext_attributes` span the specifier line and
the block it attributes rather than reusing the block's own meta.
Downstream, everything that reports where an attribute anchor *is* moves up
to the line the author wrote it on, which is what the specs already claimed:
- go-to-definition lands on `{#aside}`, not on the block below it;
- the document outline's symbol range starts there;
- a duplicate-id diagnostic underlines the id, not just the block;
- `Vault.Index.extract_attr_ids` reports that line, making its own comment
about the attribute line being "the better place to jump to" true.
`Anchors.spec_line_above` goes with it. It existed only because the AST could
not answer, and it was the one place in this module that read characters; the
parser answers now. An anchor carries `first_line`/`last_line` instead, so
`at_line` activates across an attribute's whole extent — its `{#id}` line and
the block, both of which are that anchor — while a caret id still answers only
on the line its `^id` closes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.