-
Notifications
You must be signed in to change notification settings - Fork 26
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feat: When an hx-ext tag is used in the current or a parent element enable autocompletion based on it #14
Comments
a query like this matches all tags where an htmx extension is active, up to 12 levels deep since tree sitter query language does not support arbitrary depth patterns yet. tree-sitter/tree-sitter#880
This might be useful for implementing conditional auto completion |
I believe this might be fixed by #16 but to be fair, I don't think people code with incomplete tags everywhere 😅 |
Ahh sorry, understood the issue incorrectly, my PR doesn't solve that :( |
If I got it right once the extension is enabled all children start to be able to use the new attrs, right? Since tree-sitter query has that limitation, maybe querying all elements isn't the way to go, but find at which point of the tree the |
yes, that is the behaviour of most extensions in HTMX and I think it would be nice if the lsp supports it.
Yeah, that might be a better way to do it, I was thinking, maybe it can be useful. I might try to make a prototype tomorrow, since I have hockey tonight. |
I have no clue how to do this. If someone wants to have a try, feel free. I can add the documentation and completions if there is a start to add to. |
I've played around a bit with implementing this. I wasn't able to completely finish, but I hope this might be helpful to build off of :) On every completion request, the basic process I followed is:
I believe this is blocked by tree-sitter/tree-sitter#2847. I've run into this issue trying to implement a somewhat similar feature on another LSP written in Rust using the tree-sitter bindings. I haven't had time to rigorously test anything yet, but here's the gist of the code I wrote to try to accomplish this: macro_rules! cursor_matches {
($cursor_line:expr,$cursor_char:expr,$query_start:expr,$query_end:expr) => {{
$query_start.row == $cursor_line
&& $query_end.row == $cursor_line
&& $query_start.column <= $cursor_char
&& $query_end.column >= $cursor_char
}};
}
/// Returns a (potentially empty) Vec of extension tags the provided position is inside of
// Currently limited by tree-sitter's max depth of 12 levels, see https://github.com/tree-sitter/tree-sitter/issues/880
pub fn get_extension_completes(text_params: TextDocumentPositionParams) -> Vec<String> {
static QUERY_HTMX_EXT: Lazy<Query> = Lazy::new(|| {
tree_sitter::Query::new(
tree_sitter_html::language(),
r#"
(
(element
(start_tag
(attribute
(attribute_name) @hxext
(quoted_attribute_value
(attribute_value) @extension
)
)
(attribute (attribute_name) @tag )?
)
(element
[
(_ (attribute (attribute_name) @tag ))
(_ (_ (attribute (attribute_name) @tag )))
(_ (_ (_ (attribute (attribute_name) @tag ))))
(_ (_ (_ (_ (attribute (attribute_name) @tag )))))
(_ (_ (_ (_ (_ (attribute (attribute_name) @tag ))))))
(_ (_ (_ (_ (_ (_ (attribute (attribute_name) @tag )))))))
(_ (_ (_ (_ (_ (_ (_ (attribute (attribute_name) @tag ))))))))
(_ (_ (_ (_ (_ (_ (_ (_ (attribute (attribute_name) @tag )))))))))
(_ (_ (_ (_ (_ (_ (_ (_ (_ (attribute (attribute_name) @tag ))))))))))
(_ (_ (_ (_ (_ (_ (_ (_ (_ (_ (attribute (attribute_name) @tag )))))))))))
(_ (_ (_ (_ (_ (_ (_ (_ (_ (_ (_ (attribute (attribute_name) @tag ))))))))))))
(_ (_ (_ (_ (_ (_ (_ (_ (_ (_ (_ (_ (attribute (attribute_name) @tag )))))))))))))
]
)
) @elem
(#match? @hxext "hx-ext")
)"#,
)
.unwrap()
});
let mut ext_tags: HashSet<String> = HashSet::new();
let mut cursor = QueryCursor::new();
let cursor_line = text_params.position.line as usize;
let cursor_char = text_params.position.character as usize;
// get the document contents and its corresponding tree-sitter tree from the store
if let Some(entry) = DOCUMENT_STORE
.get()
.expect("text store not initialized")
.lock()
.expect("text store mutex poisoned")
.get_mut(text_params.text_document.uri.as_str())
{
entry.tree = entry
.parser
.parse(entry.doc.get_content(None), entry.tree.as_ref());
if let Some(ref curr_tree) = entry.tree {
let matches = cursor.matches(
&QUERY_HTMX_EXT,
curr_tree.root_node(),
entry.doc.get_content(None).as_bytes(),
);
for match_ in matches {
let caps = match_.captures;
let extension = caps[1]
.node
.utf8_text(entry.doc.get_content(None).as_bytes())
.unwrap();
// skip @hxext and @extension, grab both @tag's if they're there
for cap in caps.iter().skip(2).take(2) {
let cap_start = cap.node.range().start_point;
let cap_end = cap.node.range().end_point;
// if the cursor is current at a tag inside the extension's scope,
// we need to add that extension's tags and attributes
if cursor_matches!(cursor_line, cursor_char, cap_start, cap_end) {
ext_tags.insert(extension.to_string());
}
}
}
}
}
ext_tags.into_iter().collect()
} My work can be found in the |
Just a thought- instead of searching the tree to find extensions present, would it be possible to allow for users to enter extensions that they frequently use? I understand it is not ideal in cases where you are working in lots of different htmx projects with different extensions in each. |
Having the option to force the lsp to display auto completion for specific extensions can be very useful when using composable templates. I've finished my last project (an animated wallpaper) so after I get nixos working decently I might have a stab at this feature. |
I spent some more time with this today and I have a skeleton in place for it to work decently well. In my own testing, the program is able to identify which extension tag(s) the cursor is in scope with. The basic flow is similar to the one detailed in my last comment in this issue, but I was able to clean things up a bit. After reparsing the given file's tree, it then issues the query for extension tags. If a match is found and the current cursor position overlaps with a Adding suggestions while inside the Not adding suggestions while outside the
@itepastra Do you still want to do this, or would you rather work on your own implementation? I'm happy to open up a draft PR to add to if you think that's the right next step. I built this off of the pending work in #43, but if that doesn't get merged I'm happy to refactor around it. All the code can be found in the extensions branch of my fork. I had a lot of fun putting this together, really hope this helps! :) |
I have had (and still have) many things on my mind but I like the progress. Will try to have a look soon(ish). |
I am unsure how to handle https://htmx.org/extensions/response-targets/, I don't think adding every statuscode as a completion is a good idea but maybe some common ones? |
Would adding the common codes from Wikipedia, Mozilla, or a similar list be sufficient? I imagine all the markdown files could be roughly the same besides some brief docs on the particular response code. For example, maybe the This extension allows you to specify different target elements to be swapped when the 404 HTTP response code is received.
[404 Not Found](https://en.wikipedia.org/wiki/HTTP_404):
The requested resource could not be found but may be available in the future. Subsequent requests by the client are permissible.
Example:
<div hx-ext="response-targets">
<div id="response-div"></div>
<button hx-post="/register"
hx-target="#response-div"
hx-target-5*="#serious-errors"
hx-target-404="#not-found">
Register!
</button>
<div id="serious-errors"></div>
<div id="not-found"></div>
</div> I'd be happy to work through the main list to create a markdown file for each common code like the example above if that would be helpful. :) Also regarding this extension, how should the program support wildcards? (e.g. 4xx or 40x) |
Some extensions, like websockets make use of some extra tags. When the extension is active (I.e. this element or a parent element has the hx-ext=extension attribute) we could also include the extension tags and attributes
an example:
I get it if this isn't added too soon, the parsing has to allow it first.
The text was updated successfully, but these errors were encountered: