Skip to content

fix(lsp): position inlay hints from the document being edited - #6092

Open
tripleaceme wants to merge 1 commit into
SQLMesh:mainfrom
tripleaceme:fix/inlay-hint-stale-position
Open

tripleaceme wants to merge 1 commit into
SQLMesh:mainfrom
tripleaceme:fix/inlay-hint-stale-position

Conversation

@tripleaceme

Copy link
Copy Markdown
Contributor

Description

Closes #5630.

Inlay hints render inside a column name while it is being typed — zip_code shows as zip::TEXT_code — and correct themselves on save, re-render or reopening the file.

The cause is not a race, debouncing, or a document-version mismatch. sqlmesh/lsp/main.py registers did_open and did_save and no textDocument/didChange handler, so the server's parsed model is frozen until the file is saved. get_hints derives every hint's character from sqlglot token metadata on lsp_context.context.get_model(...).query, which is the model parsed from disk at the last load. VS Code invalidates and re-requests hints on every change, so it is asking correctly; the server answers with last-saved offsets.

saved buffer:    1 AS big_boy              hint at character 14
edited buffer:   1 AS big_boy_column       server still returns character 14
VS Code renders: 1 AS big_boy::INT_column

Version-stamping the responses would not have helped — the server has no correct answer at any version, it would simply withhold a wrong one.

get_hints now takes the document text and reads positions from parsing that, using sqlmesh.core.dialect.parse plus _split_sql_model_statements, which is the loader's own query-extraction rule, so macro-union and jinja models behave as before. Types are still resolved by name against the loaded model. main.py passes ls.workspace.get_text_document(...).source, which pygls keeps in sync with didChange.

Behaviour, all covered by tests:

buffer state before after
unchanged since save correct byte-identical
column renamed / added hint lands inside the name no hint until the next reload
line inserted above all hints off by that shift hints shift correctly
mid-edit, unparseable positions from the stale parse no hints

A second symptom of the same cause, not reported in the issue: inserting a line above shifted all the text but not the hints, so they attached to the wrong columns with the wrong types.

Three things for your judgement

  1. Hints disappear while a column is being renamed, rather than appearing inside the word. I think that is the better of the two, but it is a UX call. The alternative is holding the last good hint and suppressing only the ones that would split a token.
  2. This parses the model file per request, and hints are requested on roughly every keystroke. It is one small file and comparable to the normalize_identifiers(query.copy()) already done per request, but I have not benchmarked it against a large model.
  3. _split_sql_model_statements is private. I imported it rather than re-implementing the rule, so the two cannot drift. sqlmesh/lsp/ already reaches into internals such as context._models and target._path, so this seemed in keeping, but you may prefer it promoted or duplicated.

Out of scope here, but worth flagging: diagnostics are published from the same reload-on-save context, so they are likely stale between saves in the same way. I have not investigated.

Test Plan

Two tests in tests/lsp/test_hints.py:

  • test_hints_are_positioned_from_the_edited_document — a column renamed in the buffer but not saved produces no hint inside the name, and the remaining hints stay correct.
  • test_hints_for_unparseable_document — mid-edit text that does not parse yields no hints rather than stale ones.

Both fail without the change, and the failure prints the bug verbatim:

assert 'SELECT customer_id::INT, zip_code' in "...SELECT customer_id::INT, zip::TEXT_code..."
pytest tests/lsp
63 passed

ruff check, ruff format --check and mypy clean on the changed files. No TypeScript touched — the extension has no inlay-hint code, it uses the stock vscode-languageclient provider.

Checklist

  • I have run make style and fixed any issues
  • I have added tests for my changes (if applicable)
  • All existing tests pass (make fast-test)
  • My commits are signed off (git commit -s) per the DCO

Inlay hint positions came from the model held by the LSP context, whose
query carries the token positions of the file as it was last loaded. The
context is only reloaded on didSave, so while the user edits a column name
the hint is placed at the offset the name used to end at, which renders the
type cast inside the name being typed (`zip::TEXT_code`). It corrects
itself on save, render or reopen because those reload the context.

Parse the text the editor currently holds and take the hint positions from
that, looking column types up by name on the loaded model. A column that
isn't on the model yet gets no hint until the next reload, and text that
doesn't parse yet gets none at all, rather than hints at offsets that no
longer match the document.

Fixes SQLMesh#5630

Signed-off-by: Adegbite Ayoade <tripleaceme@gmail.com>
@tripleaceme

Copy link
Copy Markdown
Contributor Author

@cmgoffena13 — closes #5630.

The cause turned out to be simpler than the symptom suggests: the LSP has no textDocument/didChange handler, so its parsed model is frozen until save, and the hints carry offsets from that stale parse. VS Code is requesting them correctly.

Three judgement calls are listed at the end of the description — mainly that hints now disappear while a column is being renamed rather than rendering inside the word. Also worth knowing: diagnostics come from the same reload-on-save context, so they are probably stale in the same way. I have not investigated that and it is out of scope here.

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.

vscode/lsp type casting bug

1 participant