feat(lsp): explain generated targets on hover
This commit is contained in:
@@ -63,7 +63,7 @@ Keep it stable. Prefer pointers to canonical sources over copied structure, file
|
|||||||
- Use `cargo run -p hemx-xtask -- app new PATH` for the generic page/form/keyed-row/notice starter, and `cargo run -p hemx-xtask -- app new --mobile PATH` for the phone-first starter with host capabilities, recovery truth, and release-kit commands; do not treat it as a mobile framework or store-submission bot. req: ceremony/005 req: ceremony/006 req: ceremony/007
|
- Use `cargo run -p hemx-xtask -- app new PATH` for the generic page/form/keyed-row/notice starter, and `cargo run -p hemx-xtask -- app new --mobile PATH` for the phone-first starter with host capabilities, recovery truth, and release-kit commands; do not treat it as a mobile framework or store-submission bot. req: ceremony/005 req: ceremony/006 req: ceremony/007
|
||||||
- The public component-reuse explanation lives in `docs/recipes/reusable-partials.md`; do not grow a client component framework to explain partial composition.
|
- The public component-reuse explanation lives in `docs/recipes/reusable-partials.md`; do not grow a client component framework to explain partial composition.
|
||||||
- The stable public `.heml` authoring surface lives in `docs/hemplate-syntax.md`; Hemlate examples must use that real hemplate syntax, not Vue/Handlebars sketches. hemx-build consumes hemplate Surface facts and must not grow an independent `.heml` parser or CSS-path identity model. Plain CSS/SCSS owns appearance; generated class constants are ergonomic references, not a styling framework or behavior selector system. Generated resources, form/handle metadata, atoms, and event constants come from hemx-build facts, not hand-written app plumbing. Forms remain HTML-shaped, checked against user-authored Rust domain types, parsed through `FormValue`, and manipulated through generated form/control ids rather than selectors. Proc-macros stay local/side-effect-free while build.rs owns global codegen and hard build failures. No-op global codegen must preserve generated artifact timestamps so downstream Rust compilation remains fresh only when canonical output changes. req: boundary/001 req: boundary/002 req: boundary/003 req: boundary/004 req: surface/001 req: surface/002 req: surface/003 req: surface/004 req: surface/005 req: surface/006 req: surface/007 req: surface/008 req: surface/009 req: surface/010 req: codegen/001 req: codegen/003 req: codegen/004 req: codegen/005 req: codegen/006 req: form/001 req: form/004 req: form/007 req: form/008 req: form_effects/001 req: form_effects/002 req: form_effects/003 req: build/001 req: build/002 req: build/003 req: build/004 req: build/005 req: build/006 req: build/007 req: build/008 req: build/009 req: style/001 req: style/002 req: style/003 req: style/004 req: style/005 req: style/006
|
- The stable public `.heml` authoring surface lives in `docs/hemplate-syntax.md`; Hemlate examples must use that real hemplate syntax, not Vue/Handlebars sketches. hemx-build consumes hemplate Surface facts and must not grow an independent `.heml` parser or CSS-path identity model. Plain CSS/SCSS owns appearance; generated class constants are ergonomic references, not a styling framework or behavior selector system. Generated resources, form/handle metadata, atoms, and event constants come from hemx-build facts, not hand-written app plumbing. Forms remain HTML-shaped, checked against user-authored Rust domain types, parsed through `FormValue`, and manipulated through generated form/control ids rather than selectors. Proc-macros stay local/side-effect-free while build.rs owns global codegen and hard build failures. No-op global codegen must preserve generated artifact timestamps so downstream Rust compilation remains fresh only when canonical output changes. req: boundary/001 req: boundary/002 req: boundary/003 req: boundary/004 req: surface/001 req: surface/002 req: surface/003 req: surface/004 req: surface/005 req: surface/006 req: surface/007 req: surface/008 req: surface/009 req: surface/010 req: codegen/001 req: codegen/003 req: codegen/004 req: codegen/005 req: codegen/006 req: form/001 req: form/004 req: form/007 req: form/008 req: form_effects/001 req: form_effects/002 req: form_effects/003 req: build/001 req: build/002 req: build/003 req: build/004 req: build/005 req: build/006 req: build/007 req: build/008 req: build/009 req: style/001 req: style/002 req: style/003 req: style/004 req: style/005 req: style/006
|
||||||
- Optional `.heml` editor overlays must share authority with `hemx-build` diagnostics and `docs/hemplate-syntax.md`; `hemx-lsp` owns editor protocol glue for diagnostics/completion/hover and derive-known template facts, while VS Code/Cursor/Neovim keep normal HTML/tree-sitter tooling. Do not create a second template language, selector model, formatter, Rust type system, or custom editor framework. Compiler diagnostics with directive/target metadata select that source attribute instead of line 0 column 0. Cross-file template/handler references visible to build validation must fail at `cargo check` with useful spans; global completeness checks stay component-scoped unless caught at mount/tests. req: diagnostics/004 req: diagnostics/005 req: diagnostics/006 req: diagnostics/007 req: diagnostics/008 req: diag/009 req: invariant/004 req: invariant/006 req: check/001 req: check/003
|
- Optional `.heml` editor overlays must share authority with `hemx-build` diagnostics and `docs/hemplate-syntax.md`; `hemx-lsp` owns editor protocol glue for diagnostics/completion/hover and derive-known template facts, while VS Code/Cursor/Neovim keep normal HTML/tree-sitter tooling. Do not create a second template language, selector model, formatter, Rust type system, or custom editor framework. Compiler diagnostics with directive/target metadata select that source attribute instead of line 0 column 0. Cross-file template/handler references visible to build validation must fail at `cargo check` with useful spans; global completeness checks stay component-scoped unless caught at mount/tests. req: diagnostics/004 req: diagnostics/005 req: diagnostics/006 req: diagnostics/007 req: diagnostics/008 req: diag/009 req: diag/010 req: invariant/004 req: invariant/006 req: check/001 req: check/003
|
||||||
- JS runtime changes must preserve root-scoped lookup, delegated listeners, postcard EffectBatch application, fail-closed request handling, transactional/recoverable failure behavior, root-scoped error outlets, and tiny pending/failure/trigger-timing conventions without selectors, handler-name parsing, VDOM, expressions, or per-node listeners. Runtime `.d.ts` types are developer convenience only, not core tooling authority. req: invariant/002 req: runtime/001 req: runtime/002 req: runtime/003 req: runtime/005 req: runtime/006 req: failure/001 req: failure/002 req: failure/003 req: failure/004 req: failure/005 req: failure/006 req: convention/001 req: convention/002 req: convention/003 req: convention/004 req: convention/005 req: convention/006 req: convention/007 req: convention/008 req: convention/009 req: convention/010 req: convention/011 req: convention/012 req: convention/013 req: convention/014 req: convention/015 req: convention/016 req: convention/017 req: ts/001
|
- JS runtime changes must preserve root-scoped lookup, delegated listeners, postcard EffectBatch application, fail-closed request handling, transactional/recoverable failure behavior, root-scoped error outlets, and tiny pending/failure/trigger-timing conventions without selectors, handler-name parsing, VDOM, expressions, or per-node listeners. Runtime `.d.ts` types are developer convenience only, not core tooling authority. req: invariant/002 req: runtime/001 req: runtime/002 req: runtime/003 req: runtime/005 req: runtime/006 req: failure/001 req: failure/002 req: failure/003 req: failure/004 req: failure/005 req: failure/006 req: convention/001 req: convention/002 req: convention/003 req: convention/004 req: convention/005 req: convention/006 req: convention/007 req: convention/008 req: convention/009 req: convention/010 req: convention/011 req: convention/012 req: convention/013 req: convention/014 req: convention/015 req: convention/016 req: convention/017 req: ts/001
|
||||||
- Opaque island JavaScript is a leaf adapter for high-frequency local behavior only; client-local handlers keep the server-handler shape while `hemx-wasm` owns concrete opt-in syntax. Use native events/generated helpers at the boundary and do not introduce a component runtime, client state graph, VDOM, selector interop, or second UI model. req: canonical_authoring/017 req: client_local/001 req: client_local/003 req: client_local/004 req: interop/001 req: interop/002 req: interop/003 req: interop/006 req: interop/007 req: interop/008 req: interop/009 req: interop/010 req: interop/011 req: interop/012
|
- Opaque island JavaScript is a leaf adapter for high-frequency local behavior only; client-local handlers keep the server-handler shape while `hemx-wasm` owns concrete opt-in syntax. Use native events/generated helpers at the boundary and do not introduce a component runtime, client state graph, VDOM, selector interop, or second UI model. req: canonical_authoring/017 req: client_local/001 req: client_local/003 req: client_local/004 req: interop/001 req: interop/002 req: interop/003 req: interop/006 req: interop/007 req: interop/008 req: interop/009 req: interop/010 req: interop/011 req: interop/012
|
||||||
- Host capability adapters must stay at the `hemx-host` boundary: typed capabilities use fire/request/stream/schedule shapes; adapters may call host APIs and return host events, but they must not mutate DOM or own app/domain state. req: host/001 req: host/002
|
- Host capability adapters must stay at the `hemx-host` boundary: typed capabilities use fire/request/stream/schedule shapes; adapters may call host APIs and return host events, but they must not mutate DOM or own app/domain state. req: host/001 req: host/002
|
||||||
|
|||||||
@@ -828,6 +828,9 @@ what a valid business email is. [north_star]
|
|||||||
### req: diag/009
|
### req: diag/009
|
||||||
0 009 `hemx-lsp` diagnostics select the offending directive and target in the current `.heml` source when compiler metadata identifies them, rather than defaulting every error to line 0 column 0. [north_star]
|
0 009 `hemx-lsp` diagnostics select the offending directive and target in the current `.heml` source when compiler metadata identifies them, rather than defaulting every error to line 0 column 0. [north_star]
|
||||||
|
|
||||||
|
### req: diag/010
|
||||||
|
0 010 Hovering a generated `data-hemx-root`, slot, form, or handle value reports its generated resource kind and Rust `ui::<name>` symbol from current `hemx-build` facts. [north_star]
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## test
|
## test
|
||||||
|
|||||||
@@ -96,7 +96,9 @@ repo-owned fixture and golden capture contract live in
|
|||||||
Use the shared repo extension in `editors/vscode-hemx` for VS Code and Cursor.
|
Use the shared repo extension in `editors/vscode-hemx` for VS Code and Cursor.
|
||||||
It sets `.heml` to the built-in HTML language mode, starts `hemx-lsp`, and maps
|
It sets `.heml` to the built-in HTML language mode, starts `hemx-lsp`, and maps
|
||||||
LSP diagnostics/completion/hover into the editor without adding a separate grammar.
|
LSP diagnostics/completion/hover into the editor without adding a separate grammar.
|
||||||
req: diagnostics/005
|
Hovering a generated root, slot, form, or handle value reports its resource kind
|
||||||
|
and generated `ui::<name>` Rust symbol from the current template.
|
||||||
|
req: diagnostics/005 req: diag/010
|
||||||
|
|
||||||
When the workspace root is this repository, the extension starts:
|
When the workspace root is this repository, the extension starts:
|
||||||
|
|
||||||
|
|||||||
+52
-1
@@ -442,7 +442,10 @@ fn hemplate_completion_items(
|
|||||||
items.push(completion_item(
|
items.push(completion_item(
|
||||||
&format!("ui::{}", target.name),
|
&format!("ui::{}", target.name),
|
||||||
&format!("ui::{}", target.name),
|
&format!("ui::{}", target.name),
|
||||||
"Generated hemx target discovered by hemx-build in the open `.heml` document.",
|
&format!(
|
||||||
|
"Generated hemx {} target discovered by hemx-build in the open `.heml` document.",
|
||||||
|
target.kind
|
||||||
|
),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -502,6 +505,21 @@ fn completion_item(label: &str, insert_text: &str, detail: &str) -> serde_json::
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn hemplate_hover(uri: &str, text: &str, position: Option<(usize, usize)>) -> serde_json::Value {
|
fn hemplate_hover(uri: &str, text: &str, position: Option<(usize, usize)>) -> serde_json::Value {
|
||||||
|
if let Some((kind, name)) = position.and_then(|position| generated_target_at(text, position)) {
|
||||||
|
if let Ok(targets) =
|
||||||
|
hemx_build::generated_targets_for_heml_source(path_from_file_uri(uri), text)
|
||||||
|
{
|
||||||
|
if targets
|
||||||
|
.iter()
|
||||||
|
.any(|target| target.kind == kind && target.name == name)
|
||||||
|
{
|
||||||
|
return hover_markdown(&format!(
|
||||||
|
"Generated hemx `{kind}` target `{name}`.\n\nRust symbol: `ui::{name}`.\n\nSource: hemx-build facts for the current `.heml` document."
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let Some((owner, field)) = position.and_then(|position| dotted_name_at(text, position)) {
|
if let Some((owner, field)) = position.and_then(|position| dotted_name_at(text, position)) {
|
||||||
if let Ok(Some(facts)) =
|
if let Ok(Some(facts)) =
|
||||||
hemx_build::template_context_facts_for_heml_source(path_from_file_uri(uri), text)
|
hemx_build::template_context_facts_for_heml_source(path_from_file_uri(uri), text)
|
||||||
@@ -584,6 +602,26 @@ fn line_prefix(text: &str, position: (usize, usize)) -> Option<String> {
|
|||||||
Some(line.chars().take(position.1).collect())
|
Some(line.chars().take(position.1).collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn generated_target_at(text: &str, position: (usize, usize)) -> Option<(String, String)> {
|
||||||
|
let line = text.lines().nth(position.0)?;
|
||||||
|
let cursor = position.1.min(line.len());
|
||||||
|
[
|
||||||
|
("root", "data-hemx-root=\""),
|
||||||
|
("slot", "data-hemx-slot=\""),
|
||||||
|
("form", "data-hemx-form=\""),
|
||||||
|
("handle", "data-hemx-handle=\""),
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.find_map(|(kind, prefix)| {
|
||||||
|
line.match_indices(prefix).find_map(|(attribute_start, _)| {
|
||||||
|
let value_start = attribute_start + prefix.len();
|
||||||
|
let value_end = value_start + line[value_start..].find('"')?;
|
||||||
|
(cursor >= value_start && cursor <= value_end)
|
||||||
|
.then(|| (kind.to_owned(), line[value_start..value_end].to_owned()))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn dotted_name_at(text: &str, position: (usize, usize)) -> Option<(String, String)> {
|
fn dotted_name_at(text: &str, position: (usize, usize)) -> Option<(String, String)> {
|
||||||
let line = text.lines().nth(position.0)?;
|
let line = text.lines().nth(position.0)?;
|
||||||
let chars = line.chars().collect::<Vec<_>>();
|
let chars = line.chars().collect::<Vec<_>>();
|
||||||
@@ -1083,6 +1121,19 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hover_reports_generated_target_kind_and_rust_symbol() {
|
||||||
|
// req: diag/010
|
||||||
|
let uri = "file:///tmp/workout.heml";
|
||||||
|
let text = r#"<main data-hemx-root="workout"><section data-hemx-slot="progress_panel"></section></main>"#;
|
||||||
|
let cursor = text.find("progress_panel").expect("fixture target") + 2;
|
||||||
|
|
||||||
|
let hover = hemplate_hover(uri, text, Some((0, cursor))).to_string();
|
||||||
|
|
||||||
|
assert!(hover.contains("`slot` target `progress_panel`"), "{hover}");
|
||||||
|
assert!(hover.contains("`ui::progress_panel`"), "{hover}");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn source_positions_use_lsp_utf16_columns() {
|
fn source_positions_use_lsp_utf16_columns() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|||||||
Reference in New Issue
Block a user