Autocomplete rsx (#2421)

* autocomplete rsx

* hide the completions module

* fix autocomplete for identifiers that start with a raw identifier

* add field completion hints for props

* remove extra import

* Revert "add field completion hints for props"

This reverts commit 194899c73783e0f9b5d601d53365746d838f2048.

* fix autocomplete inside raw expressions

* fix autofmt

* feat: just use regular ifmt if it's compatible

reuses the tokens for ifmt so RA provides its HIR lowering on simple formatted strings

* fix merging ifmt strings

* add a better error message for components called like functions

---------

Co-authored-by: Jonathan Kelley <jkelleyrtp@gmail.com>
This commit is contained in:
Evan AlmloffandJonathan Kelley authored and GitHub committed 2024-05-22 07:24:50 -05:00
1 parent 245003a5d4
commit 2523581cf4
21 files changed
+788 -217

No files matched your search

+90 -5
View File
@@ -3,7 +3,7 @@ use std::fmt::{Display, Formatter};
use super::*;
use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::quote;
use quote::{quote, quote_spanned};
use syn::{parse_quote, spanned::Spanned, Expr, ExprIf, Ident, LitStr};
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
@@ -95,6 +95,9 @@ impl AttributeType {
pub struct ElementAttrNamed {
pub el_name: ElementName,
pub attr: ElementAttr,
// If this is the last attribute of an element and it doesn't have a tailing comma,
// we add hints so that rust analyzer completes it either as an attribute or element
pub(crate) followed_by_comma: bool,
}
impl Hash for ElementAttrNamed {
@@ -112,6 +115,15 @@ impl PartialEq for ElementAttrNamed {
impl Eq for ElementAttrNamed {}
impl ElementAttrNamed {
/// Create a new ElementAttrNamed
pub fn new(el_name: ElementName, attr: ElementAttr) -> Self {
Self {
el_name,
attr,
followed_by_comma: true,
}
}
pub(crate) fn try_combine(&self, other: &Self) -> Option<Self> {
if self.el_name == other.el_name && self.attr.name == other.attr.name {
if let Some(separator) = self.attr.name.multi_attribute_separator() {
@@ -121,16 +133,64 @@ impl ElementAttrNamed {
name: self.attr.name.clone(),
value: self.attr.value.combine(separator, &other.attr.value),
},
followed_by_comma: self.followed_by_comma || other.followed_by_comma,
});
}
}
None
}
/// If this is the last attribute of an element and it doesn't have a tailing comma,
/// we add hints so that rust analyzer completes it either as an attribute or element
fn completion_hints(&self) -> TokenStream2 {
let ElementAttrNamed {
el_name,
attr,
followed_by_comma,
} = self;
// If there is a trailing comma, rust analyzer does a good job of completing the attribute by itself
if *followed_by_comma {
return quote! {};
}
// Only add hints if the attribute is:
// - a built in attribute (not a literal)
// - an build in element (not a custom element)
// - a shorthand attribute
let (
ElementName::Ident(el),
ElementAttrName::BuiltIn(name),
ElementAttrValue::Shorthand(_),
) = (el_name, &attr.name, &attr.value)
else {
return quote! {};
};
// If the attribute is a shorthand attribute, but it is an event handler, rust analyzer already does a good job of completing the attribute by itself
if name.to_string().starts_with("on") {
return quote! {};
}
quote! {
{
#[allow(dead_code)]
#[doc(hidden)]
mod __completions {
// Autocomplete as an attribute
pub use super::dioxus_elements::#el::*;
// Autocomplete as an element
pub use super::dioxus_elements::elements::completions::CompleteWithBraces::*;
fn ignore() {
#name
}
}
}
}
}
}
impl ToTokens for ElementAttrNamed {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let ElementAttrNamed { el_name, attr } = self;
let ElementAttrNamed { el_name, attr, .. } = self;
let ns = |name: &ElementAttrName| match (el_name, name) {
(ElementName::Ident(i), ElementAttrName::BuiltIn(_)) => {
@@ -186,19 +246,25 @@ impl ToTokens for ElementAttrNamed {
}
ElementAttrValue::EventTokens(tokens) => match &self.attr.name {
ElementAttrName::BuiltIn(name) => {
quote! {
quote_spanned! { tokens.span() =>
dioxus_elements::events::#name(#tokens)
}
}
ElementAttrName::Custom(_) => unreachable!("Handled elsewhere in the macro"),
},
_ => {
quote! { dioxus_elements::events::#value(#value) }
quote_spanned! { value.span() => dioxus_elements::events::#value(#value) }
}
}
};
tokens.append_all(attribute);
let completion_hints = self.completion_hints();
tokens.append_all(quote! {
{
#completion_hints
#attribute
}
});
}
}
@@ -289,6 +355,11 @@ impl ToTokens for ElementAttrValue {
}
impl ElementAttrValue {
/// Create a new ElementAttrValue::Shorthand from an Ident and normalize the identifier
pub(crate) fn shorthand(name: &Ident) -> Self {
Self::Shorthand(normalize_raw_ident(name))
}
pub fn is_shorthand(&self) -> bool {
matches!(self, ElementAttrValue::Shorthand(_))
}
@@ -388,6 +459,16 @@ impl ElementAttrValue {
}
}
// Create and normalize a built-in attribute name
// If the identifier is a reserved keyword, this method will create a raw identifier
fn normalize_raw_ident(ident: &Ident) -> Ident {
if syn::parse2::<syn::Ident>(ident.to_token_stream()).is_err() {
syn::Ident::new_raw(&ident.to_string(), ident.span())
} else {
ident.clone()
}
}
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
pub enum ElementAttrName {
BuiltIn(Ident),
@@ -395,6 +476,10 @@ pub enum ElementAttrName {
}
impl ElementAttrName {
pub(crate) fn built_in(name: &Ident) -> Self {
Self::BuiltIn(normalize_raw_ident(name))
}
fn multi_attribute_separator(&self) -> Option<&'static str> {
match self {
ElementAttrName::BuiltIn(i) => match i.to_string().as_str() {