Skip to content

LSP: TOC - #86

Merged
hon-gyu merged 3 commits into
mainfrom
hy/260804-lsp-toc
Aug 4, 2026
Merged

LSP: TOC#86
hon-gyu merged 3 commits into
mainfrom
hy/260804-lsp-toc

Conversation

@hon-gyu

@hon-gyu hon-gyu commented Aug 4, 2026

Copy link
Copy Markdown
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

hon-gyu added 3 commits August 4, 2026 20:36
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.
@hon-gyu
hon-gyu merged commit 5146141 into main Aug 4, 2026
0 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant