diff --git a/Cargo.lock b/Cargo.lock index 9ca2f7c..8900162 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -280,6 +280,7 @@ dependencies = [ "lsp-server", "lsp-types", "maplit", + "phf", "serde", "serde_json", "tree-sitter", @@ -445,6 +446,48 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +[[package]] +name = "phf" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +dependencies = [ + "phf_macros", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_macros" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "phf_shared" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project-lite" version = "0.2.10" @@ -469,6 +512,21 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + [[package]] name = "redox_syscall" version = "0.3.5" @@ -598,6 +656,12 @@ dependencies = [ "syn", ] +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + [[package]] name = "smallvec" version = "1.11.0" diff --git a/lsp/Cargo.toml b/lsp/Cargo.toml index ff76d08..e16324c 100644 --- a/lsp/Cargo.toml +++ b/lsp/Cargo.toml @@ -19,3 +19,4 @@ htmx-lsp-util = { version = "0.1", path = "../util" } tree-sitter.workspace = true tree-sitter-html.workspace = true maplit = "1.0.2" +phf = { version = "0.11.2", features = ["macros"] } diff --git a/lsp/src/handle.rs b/lsp/src/handle.rs index b83f50b..b0bf6a3 100644 --- a/lsp/src/handle.rs +++ b/lsp/src/handle.rs @@ -129,7 +129,7 @@ fn handle_completion(req: Request) -> Option { ); Some(HtmxResult::AttributeCompletion(HtmxAttributeCompletion { - items, + items: items.to_vec(), id: req.id, })) } @@ -154,7 +154,7 @@ fn handle_hover(req: Request) -> Option { Some(HtmxResult::AttributeHover(HtmxAttributeHoverResult { id: req.id, - value: attribute.desc, + value: attribute.desc.to_string(), })) } @@ -189,14 +189,12 @@ pub fn handle_other(msg: Message) -> Option { #[cfg(test)] mod tests { use super::{handle_request, HtmxResult, Request}; - use crate::htmx; use crate::text_store::{init_text_store, TEXT_STORE}; use std::sync::Once; static SETUP: Once = Once::new(); fn prepare_store(file: &str, content: &str) { SETUP.call_once(|| { - htmx::init_hx_tags(); init_text_store(); }); diff --git a/lsp/src/htmx/mod.rs b/lsp/src/htmx/mod.rs index 1c0db89..460ca3c 100644 --- a/lsp/src/htmx/mod.rs +++ b/lsp/src/htmx/mod.rs @@ -1,14 +1,13 @@ use log::debug; use lsp_types::TextDocumentPositionParams; use serde::{Deserialize, Serialize}; -use std::{collections::HashMap, path::PathBuf, sync::OnceLock}; use crate::tree_sitter::Position; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct HxCompletion { - pub name: String, - pub desc: String, + pub name: &'static str, + pub desc: &'static str, } #[derive(Clone, Debug, Serialize, Deserialize)] @@ -17,235 +16,182 @@ pub struct HxHover { pub desc: String, } -impl From<&(&str, &str)> for HxCompletion { - fn from((name, desc): &(&str, &str)) -> Self { - Self { - name: name.to_string(), - desc: desc.to_string(), - } - } -} - -impl TryFrom<&(PathBuf, String)> for HxCompletion { - type Error = anyhow::Error; - - fn try_from((path, desc): &(PathBuf, String)) -> Result { - match path.to_str() { - None | Some("") => anyhow::bail!("Invalid path"), - Some(name) => Ok(Self { - name: name.to_string(), - desc: desc.to_string(), - }), - } - } +macro_rules! build_completion { + ($(($name:expr, $desc:expr)),*) => { + &[ + $(HxCompletion { + name: $name, + desc: include_str!($desc), + }),* + ] + }; } -pub fn hx_completion(text_params: TextDocumentPositionParams) -> Option> { +pub fn hx_completion(text_params: TextDocumentPositionParams) -> Option<&'static [HxCompletion]> { let result = crate::tree_sitter::get_position_from_lsp_completion(text_params.clone())?; debug!("result: {:?} params: {:?}", result, text_params); match result { - Position::AttributeName(name) => { - if name.starts_with("hx-") { - return HX_TAGS.get().cloned(); - } - } - - Position::AttributeValue { name, .. } => { - let values = HX_ATTRIBUTE_VALUES.get()?.get(&name)?; - return Some(values.clone()); - } - }; - - None + Position::AttributeName(name) => name.starts_with("hx-").then_some(HX_TAGS), + Position::AttributeValue { name, .. } => HX_ATTRIBUTE_VALUES.get(&name).copied(), + } } pub fn hx_hover(text_params: TextDocumentPositionParams) -> Option { - let result = crate::tree_sitter::get_position_from_lsp_completion(text_params)?; + let result = crate::tree_sitter::get_position_from_lsp_completion(text_params.clone())?; debug!("handle_hover result: {:?}", result); match result { - Position::AttributeName(name) => HX_TAGS - .get() - .expect("Why it can't get HX_TAGS?") - .iter() - .find(|x| x.name == name) - .cloned(), - - Position::AttributeValue { name, .. } => HX_TAGS - .get() - .expect("Why it can't get HX_TAGS?") - .iter() - .find(|x| x.name == name) - .cloned(), + Position::AttributeName(name) => HX_TAGS.iter().find(|x| x.name == name).cloned(), + Position::AttributeValue { name, .. } => HX_TAGS.iter().find(|x| x.name == name).cloned(), } } -pub static HX_TAGS: OnceLock> = OnceLock::new(); -pub static HX_ATTRIBUTE_VALUES: OnceLock>> = OnceLock::new(); - -fn to_hx_completion(values: Vec<(&str, &str)>) -> Vec { - return values.iter().filter_map(|x| x.try_into().ok()).collect(); -} - -pub fn init_hx_tags() { - _ = HX_ATTRIBUTE_VALUES.set(maplit::hashmap! { - String::from("hx-swap") => to_hx_completion(vec![ - ("innerHTML", include_str!("./hx-swap/innerHTML.md")), - ("outerHTML", include_str!("./hx-swap/outerHTML.md")), - ("afterbegin", include_str!("./hx-swap/afterbegin.md")), - ("afterend", include_str!("./hx-swap/afterend.md")), - ("beforebegin", include_str!("./hx-swap/beforebegin.md")), - ("beforeend", include_str!("./hx-swap/beforeend.md")), - ("delete", include_str!("./hx-swap/delete.md")), - ("none", include_str!("./hx-swap/none.md")), - ]), - - String::from("hx-target") => to_hx_completion(vec![ - ("closest", include_str!("./hx-target/closest.md")), - ("find", include_str!("./hx-target/find.md")), - ("next", include_str!("./hx-target/next.md")), - ("prev", include_str!("./hx-target/prev.md")), - ("this", include_str!("./hx-target/this.md")), - ]), - - String::from("hx-boost") => to_hx_completion(vec![ - ("true", include_str!("./hx-boost/true.md")), - ("false", include_str!("./hx-boost/false.md")), - ]), - - String::from("hx-disabled-elt") => to_hx_completion(vec![ - ("closest", include_str!("./hx-disabled-elt/closest.md")), - ("this", include_str!("./hx-disabled-elt/this.md")), - ]), - - String::from("hx-trigger") => to_hx_completion(vec![ - ("click", include_str!("./hx-trigger/click.md")), - ("once", include_str!("./hx-trigger/once.md")), - ("changed", include_str!("./hx-trigger/changed.md")), - ("delay:", include_str!("./hx-trigger/delay.md")), - ("throttle:", include_str!("./hx-trigger/throttle.md")), - ("from:", include_str!("./hx-trigger/from.md")), - ("target:", include_str!("./hx-trigger/target.md")), - ("consume", include_str!("./hx-trigger/consume.md")), - ("queue:", include_str!("./hx-trigger/queue.md")), - ("keyup", include_str!("./hx-trigger/keyup.md")), - ("load", include_str!("./hx-trigger/load.md")), - ("revealed", include_str!("./hx-trigger/revealed.md")), - ("intersect", include_str!("./hx-trigger/intersect.md")), - ("every", include_str!("./hx-trigger/every.md")), - ]), - - String::from("hx-ext") => to_hx_completion(vec![ - ("ajax-header", include_str!("./hx-ext/ajax-header.md")), - ("alpine-morph", include_str!("./hx-ext/alpine-morph.md")), - ("class-tools", include_str!("./hx-ext/class-tools.md")), - ("client-side-templates", include_str!("./hx-ext/client-side-templates.md")), - ("debug", include_str!("./hx-ext/debug.md")), - ("disable-element", include_str!("./hx-ext/disable-element.md")), - ("event-header", include_str!("./hx-ext/event-header.md")), - ("head-support", include_str!("./hx-ext/head-support.md")), - ("include-vals", include_str!("./hx-ext/include-vals.md")), - ("json-enc", include_str!("./hx-ext/json-enc.md")), - ("morph", include_str!("./hx-ext/morph.md")), - ("loading-states", include_str!("./hx-ext/loading-states.md")), - ("method-override", include_str!("./hx-ext/method-override.md")), - ("morphdom-swap", include_str!("./hx-ext/morphdom-swap.md")), - ("multi-swap", include_str!("./hx-ext/multi-swap.md")), - ("path-deps", include_str!("./hx-ext/path-deps.md")), - ("preload", include_str!("./hx-ext/preload.md")), - ("remove-me", include_str!("./hx-ext/remove-me.md")), - ("response-targets", include_str!("./hx-ext/response-targets.md")), - ("restored", include_str!("./hx-ext/restored.md")), - ("sse", include_str!("./hx-ext/sse.md")), - ("ws", include_str!("./hx-ext/ws.md")), - ]), - - String::from("hx-push-url") => to_hx_completion(vec![ - ("true", include_str!("./hx-push-url/true.md")), - ("false", include_str!("./hx-push-url/false.md")), - ]), - - String::from("hx-swap-oob") => to_hx_completion(vec![ - ("true", include_str!("./hx-swap-oob/true.md")), - ("innerHTML", include_str!("./hx-swap/innerHTML.md")), - ("outerHTML", include_str!("./hx-swap/outerHTML.md")), - ("afterbegin", include_str!("./hx-swap/afterbegin.md")), - ("afterend", include_str!("./hx-swap/afterend.md")), - ("beforebegin", include_str!("./hx-swap/beforebegin.md")), - ("beforeend", include_str!("./hx-swap/beforeend.md")), - ("delete", include_str!("./hx-swap/delete.md")), - ("none", include_str!("./hx-swap/none.md")), - ]), - - String::from("hx-history") => to_hx_completion(vec![ - ("false", include_str!("./hx-history/false.md")), - ]), - - String::from("hx-params") => to_hx_completion(vec![ - ("*", include_str!("./hx-params/star.md")), - ("none", include_str!("./hx-params/none.md")), - ("not", include_str!("./hx-params/not.md")), - ]), - - String::from("hx-replace-url") => to_hx_completion(vec![ - ("true", include_str!("./hx-replace-url/true.md")), - ("false", include_str!("./hx-replace-url/false.md")), - ]), - - String::from("hx-sync") => to_hx_completion(vec![ - ("drop", include_str!("./hx-sync/drop.md")), - ("abort", include_str!("./hx-sync/abort.md")), - ("replace", include_str!("./hx-sync/replace.md")), - ("queue", include_str!("./hx-sync/queue.md")), - ]) - }); - - _ = HX_TAGS.set(to_hx_completion(vec![ - ("hx-boost", include_str!("./attributes/hx-boost.md")), - ("hx-delete", include_str!("./attributes/hx-delete.md")), - ("hx-get", include_str!("./attributes/hx-get.md")), - ("hx-include", include_str!("./attributes/hx-include.md")), - ("hx-patch", include_str!("./attributes/hx-patch.md")), - ("hx-post", include_str!("./attributes/hx-post.md")), - ("hx-put", include_str!("./attributes/hx-put.md")), - ("hx-swap", include_str!("./attributes/hx-swap.md")), - ("hx-target", include_str!("./attributes/hx-target.md")), - ("hx-trigger", include_str!("./attributes/hx-trigger.md")), - ("hx-vals", include_str!("./attributes/hx-vals.md")), - ("hx-push-url", include_str!("./attributes/hx-push-url.md")), - ("hx-select", include_str!("./attributes/hx-select.md")), - ("hx-ext", include_str!("./attributes/hx-ext.md")), - ("hx-on", include_str!("./attributes/hx-on.md")), - ( - "hx-select-oob", - include_str!("./attributes/hx-select-oob.md"), - ), - ("hx-swap-oob", include_str!("./attributes/hx-swap-oob.md")), - ("hx-confirm", include_str!("./attributes/hx-confirm.md")), - ("hx-disable", include_str!("./attributes/hx-disable.md")), - ( - "hx-disabled-elt", - include_str!("./attributes/hx-disabled-elt.md"), - ), - ("hx-encoding", include_str!("./attributes/hx-encoding.md")), - ("hx-headers", include_str!("./attributes/hx-headers.md")), - ("hx-history", include_str!("./attributes/hx-history.md")), - ( - "hx-history-elt", - include_str!("./attributes/hx-history-elt.md"), - ), - ("hx-indicator", include_str!("./attributes/hx-indicator.md")), - ("hx-params", include_str!("./attributes/hx-params.md")), - ("hx-preserve", include_str!("./attributes/hx-preserve.md")), - ("hx-prompt", include_str!("./attributes/hx-prompt.md")), - ( - "hx-replace-url", - include_str!("./attributes/hx-replace-url.md"), - ), - ("hx-request", include_str!("./attributes/hx-request.md")), - ("hx-sync", include_str!("./attributes/hx-sync.md")), - ("hx-validate", include_str!("./attributes/hx-validate.md")), - ])); -} +pub static HX_TAGS: &[HxCompletion] = build_completion!( + ("hx-boost", "./attributes/hx-boost.md"), + ("hx-delete", "./attributes/hx-delete.md"), + ("hx-get", "./attributes/hx-get.md"), + ("hx-include", "./attributes/hx-include.md"), + ("hx-patch", "./attributes/hx-patch.md"), + ("hx-post", "./attributes/hx-post.md"), + ("hx-put", "./attributes/hx-put.md"), + ("hx-swap", "./attributes/hx-swap.md"), + ("hx-target", "./attributes/hx-target.md"), + ("hx-trigger", "./attributes/hx-trigger.md"), + ("hx-vals", "./attributes/hx-vals.md"), + ("hx-push-url", "./attributes/hx-push-url.md"), + ("hx-select", "./attributes/hx-select.md"), + ("hx-ext", "./attributes/hx-ext.md"), + ("hx-on", "./attributes/hx-on.md"), + ("hx-select-oob", "./attributes/hx-select-oob.md"), + ("hx-swap-oob", "./attributes/hx-swap-oob.md"), + ("hx-confirm", "./attributes/hx-confirm.md"), + ("hx-disable", "./attributes/hx-disable.md"), + ("hx-disabled-elt", "./attributes/hx-disabled-elt.md"), + ("hx-encoding", "./attributes/hx-encoding.md"), + ("hx-headers", "./attributes/hx-headers.md"), + ("hx-history", "./attributes/hx-history.md"), + ("hx-history-elt", "./attributes/hx-history-elt.md"), + ("hx-indicator", "./attributes/hx-indicator.md"), + ("hx-params", "./attributes/hx-params.md"), + ("hx-preserve", "./attributes/hx-preserve.md"), + ("hx-prompt", "./attributes/hx-prompt.md"), + ("hx-replace-url", "./attributes/hx-replace-url.md"), + ("hx-request", "./attributes/hx-request.md"), + ("hx-sync", "./attributes/hx-sync.md"), + ("hx-validate", "./attributes/hx-validate.md") +); + +pub static HX_ATTRIBUTE_VALUES: phf::Map<&'static str, &[HxCompletion]> = phf::phf_map! { + "hx-swap" => + build_completion![ + ("innerHTML", "./hx-swap/innerHTML.md"), + ("outerHTML", "./hx-swap/outerHTML.md"), + ("afterbegin", "./hx-swap/afterbegin.md"), + ("afterend", "./hx-swap/afterend.md"), + ("beforebegin", "./hx-swap/beforebegin.md"), + ("beforeend", "./hx-swap/beforeend.md"), + ("delete", "./hx-swap/delete.md"), + ("none", "./hx-swap/none.md") + ] as &[_], + + "hx-target" => build_completion![ + ("closest", "./hx-target/closest.md"), + ("find", "./hx-target/find.md"), + ("next", "./hx-target/next.md"), + ("prev", "./hx-target/prev.md"), + ("this", "./hx-target/this.md") + ] as &[_], + + "hx-boost" => build_completion![ + ("true", "./hx-boost/true.md"), + ("false", "./hx-boost/false.md") + ] as &[_], + + "hx-disabled-elt" => build_completion![ + ("closest", "./hx-disabled-elt/closest.md"), + ("this", "./hx-disabled-elt/this.md") + ] as &[_], + + "hx-trigger" => build_completion![ + ("click", "./hx-trigger/click.md"), + ("once", "./hx-trigger/once.md"), + ("changed", "./hx-trigger/changed.md"), + ("delay:", "./hx-trigger/delay.md"), + ("throttle:", "./hx-trigger/throttle.md"), + ("from:", "./hx-trigger/from.md"), + ("target:", "./hx-trigger/target.md"), + ("consume", "./hx-trigger/consume.md"), + ("queue:", "./hx-trigger/queue.md"), + ("keyup", "./hx-trigger/keyup.md"), + ("load", "./hx-trigger/load.md"), + ("revealed", "./hx-trigger/revealed.md"), + ("intersect", "./hx-trigger/intersect.md"), + ("every", "./hx-trigger/every.md") + ] as &[_], + + "hx-ext" => build_completion![ + ("ajax-header", "./hx-ext/ajax-header.md"), + ("alpine-morph", "./hx-ext/alpine-morph.md"), + ("class-tools", "./hx-ext/class-tools.md"), + ("client-side-templates", "./hx-ext/client-side-templates.md"), + ("debug", "./hx-ext/debug.md"), + ("disable-element", "./hx-ext/disable-element.md"), + ("event-header", "./hx-ext/event-header.md"), + ("head-support", "./hx-ext/head-support.md"), + ("include-vals", "./hx-ext/include-vals.md"), + ("json-enc", "./hx-ext/json-enc.md"), + ("morph", "./hx-ext/morph.md"), + ("loading-states", "./hx-ext/loading-states.md"), + ("method-override", "./hx-ext/method-override.md"), + ("morphdom-swap", "./hx-ext/morphdom-swap.md"), + ("multi-swap", "./hx-ext/multi-swap.md"), + ("path-deps", "./hx-ext/path-deps.md"), + ("preload", "./hx-ext/preload.md"), + ("remove-me", "./hx-ext/remove-me.md"), + ("response-targets", "./hx-ext/response-targets.md"), + ("restored", "./hx-ext/restored.md"), + ("sse", "./hx-ext/sse.md"), + ("ws", "./hx-ext/ws.md") + ] as &[_], + + "hx-push-url" => build_completion![ + ("true", "./hx-push-url/true.md"), + ("false", "./hx-push-url/false.md") + ] as &[_], + + "hx-swap-oob" => build_completion![ + ("true", "./hx-swap-oob/true.md"), + ("innerHTML", "./hx-swap/innerHTML.md"), + ("outerHTML", "./hx-swap/outerHTML.md"), + ("afterbegin", "./hx-swap/afterbegin.md"), + ("afterend", "./hx-swap/afterend.md"), + ("beforebegin", "./hx-swap/beforebegin.md"), + ("beforeend", "./hx-swap/beforeend.md"), + ("delete", "./hx-swap/delete.md"), + ("none", "./hx-swap/none.md") + ] as &[_], + + "hx-history" => build_completion![ + ("false", "./hx-history/false.md") + ] as &[_], + + "hx-params" => build_completion!( + ("*", "./hx-params/star.md"), + ("none", "./hx-params/none.md"), + ("not", "./hx-params/not.md") + ) as &[_], + + "hx-replace-url" => build_completion![ + ("true", "./hx-replace-url/true.md"), + ("false", "./hx-replace-url/false.md") + ] as &[_], + + "hx-sync" => build_completion![ + ("drop", "./hx-sync/drop.md"), + ("abort", "./hx-sync/abort.md"), + ("replace", "./hx-sync/replace.md"), + ("queue", "./hx-sync/queue.md") + ] as &[_] +}; diff --git a/lsp/src/lib.rs b/lsp/src/lib.rs index bca855f..ffffae3 100644 --- a/lsp/src/lib.rs +++ b/lsp/src/lib.rs @@ -17,7 +17,6 @@ use lsp_server::{Connection, Message, Response}; use crate::{ handle::{handle_notification, handle_other, handle_request, HtmxResult}, - htmx::init_hx_tags, text_store::init_text_store, }; @@ -27,10 +26,10 @@ fn to_completion_list(items: Vec) -> CompletionList { items: items .iter() .map(|x| CompletionItem { - label: x.name.clone(), + label: x.name.to_string(), label_details: None, kind: Some(CompletionItemKind::TEXT), - detail: Some(x.desc.clone()), + detail: Some(x.desc.to_string()), documentation: None, deprecated: Some(false), preselect: None, @@ -114,7 +113,6 @@ fn main_loop(connection: Connection, params: serde_json::Value) -> Result<()> { pub fn start_lsp() -> Result<()> { init_text_store(); - init_hx_tags(); // Note that we must have our logging only write out to stderr. info!("starting generic LSP server");