Merge branch 'upstream' into desktop-hot-reload

This commit is contained in:
Evan Almloff committed 2023-01-13 17:43:32 -06:00
commit 8a7cee529c
16 files changed
+528 -95

No files matched your search

+9 -4
View File
@@ -67,7 +67,8 @@ impl Writer {
// check if we have a lot of attributes
let attr_len = self.is_short_attrs(attributes);
let is_short_attr_list = attr_len < 80;
let is_small_children = self.is_short_children(children).is_some();
let children_len = self.is_short_children(children);
let is_small_children = children_len.is_some();
// if we have few attributes and a lot of children, place the attrs on top
if is_short_attr_list && !is_small_children {
@@ -85,7 +86,11 @@ impl Writer {
// if we have few children and few attributes, make it a one-liner
if is_short_attr_list && is_small_children {
opt_level = ShortOptimization::Oneliner;
if children_len.unwrap() + attr_len + self.out.indent * 4 < 100 {
opt_level = ShortOptimization::Oneliner;
} else {
opt_level = ShortOptimization::PropsOnTop;
}
}
// If there's nothing at all, empty optimization
@@ -360,8 +365,8 @@ impl Writer {
Some(len) => total_count += len,
None => return None,
},
BodyNode::ForLoop(_) => todo!(),
BodyNode::IfChain(_) => todo!(),
BodyNode::ForLoop(_forloop) => return None,
BodyNode::IfChain(_chain) => return None,
}
}
+3 -3
View File
@@ -1,16 +1,16 @@
//! pretty printer for rsx!
use std::fmt::{Result, Write};
use proc_macro2::Span;
use crate::Writer;
impl Writer {
pub fn write_raw_expr(&mut self, exp: &syn::Expr) -> Result {
pub fn write_raw_expr(&mut self, placement: Span) -> Result {
/*
We want to normalize the expr to the appropriate indent level.
*/
use syn::spanned::Spanned;
let placement = exp.span();
let start = placement.start();
let end = placement.end();
+21 -16
View File
@@ -46,6 +46,8 @@ pub fn fmt_file(contents: &str) -> Vec<FormattedBlock> {
use triple_accel::{levenshtein_search, Match};
for Match { end, start, k } in levenshtein_search(b"rsx! {", contents.as_bytes()) {
let open = end;
if k > 1 {
continue;
}
@@ -55,7 +57,7 @@ pub fn fmt_file(contents: &str) -> Vec<FormattedBlock> {
continue;
}
let mut indent_level = {
let indent_level = {
// walk backwards from start until we find a new line
let mut lines = contents[..start].lines().rev();
match lines.next() {
@@ -70,31 +72,34 @@ pub fn fmt_file(contents: &str) -> Vec<FormattedBlock> {
}
};
let remaining = &contents[end - 1..];
let bracket_end = find_bracket_end(remaining).unwrap();
let sub_string = &contents[end..bracket_end + end - 1];
last_bracket_end = bracket_end + end - 1;
let remaining = &contents[open - 1..];
let close = find_bracket_end(remaining).unwrap();
// Move the last bracket end to the end of this block to avoid nested blocks
last_bracket_end = close + open - 1;
let mut new = fmt_block(sub_string, indent_level).unwrap();
// Format the substring, doesn't include the outer brackets
let substring = &remaining[1..close - 1];
// make sure to add back whatever weird whitespace there was at the end
let mut remaining_whitespace = substring.chars().rev().take_while(|c| *c == ' ').count();
let mut new = fmt_block(substring, indent_level).unwrap();
// if the new string is not multiline, don't try to adjust the marker ending
// We want to trim off any indentation that there might be
if new.len() <= 80 && !new.contains('\n') {
new = format!(" {new} ");
// if the new string is not multiline, don't try to adjust the marker ending
// We want to trim off any indentation that there might be
indent_level = 0;
remaining_whitespace = 0;
}
let end_marker = end + bracket_end - indent_level * 4 - 1;
if new == contents[end..end_marker] {
if new == substring {
continue;
}
formatted_blocks.push(FormattedBlock {
formatted: new,
start: end,
end: end_marker,
start: open,
end: last_bracket_end - remaining_whitespace - 1,
});
}
@@ -118,7 +123,7 @@ pub fn write_block_out(body: CallBody) -> Option<String> {
}
pub fn fmt_block(block: &str, indent_level: usize) -> Option<String> {
let body = syn::parse_str::<dioxus_rsx::CallBody>(block).ok()?;
let body = syn::parse_str::<dioxus_rsx::CallBody>(block).unwrap();
let mut buf = Writer {
src: block.lines().map(|f| f.to_string()).collect(),
+5 -2
View File
@@ -1,14 +1,17 @@
pub fn find_bracket_end(contents: &str) -> Option<usize> {
let mut depth = 0;
for (i, c) in contents.chars().enumerate() {
let mut len = 0;
for c in contents.chars() {
len += c.len_utf8();
if c == '{' {
depth += 1;
} else if c == '}' {
depth -= 1;
if depth == 0 {
return Some(i);
return Some(len);
}
}
}
+31 -4
View File
@@ -1,10 +1,11 @@
use dioxus_rsx::{BodyNode, ElementAttr, ElementAttrNamed};
use dioxus_rsx::{BodyNode, ElementAttr, ElementAttrNamed, ForLoop};
use proc_macro2::{LineColumn, Span};
use quote::ToTokens;
use std::{
collections::{HashMap, VecDeque},
fmt::{Result, Write},
};
use syn::{spanned::Spanned, Expr};
use syn::{spanned::Spanned, Expr, ExprIf};
use crate::buffer::Buffer;
@@ -37,8 +38,9 @@ impl Writer {
BodyNode::Element(el) => self.write_element(el),
BodyNode::Component(component) => self.write_component(component),
BodyNode::Text(text) => self.out.write_text(text),
BodyNode::RawExpr(exp) => self.write_raw_expr(exp),
_ => Ok(()),
BodyNode::RawExpr(exp) => self.write_raw_expr(exp.span()),
BodyNode::ForLoop(forloop) => self.write_for_loop(forloop),
BodyNode::IfChain(ifchain) => self.write_if_chain(ifchain),
}
}
@@ -176,6 +178,31 @@ impl Writer {
.or_insert_with(|| prettyplease::unparse_expr(expr))
.as_str()
}
fn write_for_loop(&mut self, forloop: &ForLoop) -> std::fmt::Result {
write!(
self.out,
"for {} in {} {{",
forloop.pat.clone().into_token_stream(),
prettyplease::unparse_expr(&forloop.expr)
)?;
if forloop.body.is_empty() {
write!(self.out, "}}")?;
return Ok(());
}
self.write_body_indented(&forloop.body)?;
self.out.tabbed_line()?;
write!(self.out, "}}")?;
Ok(())
}
fn write_if_chain(&mut self, ifchain: &ExprIf) -> std::fmt::Result {
self.write_raw_expr(ifchain.span())
}
}
trait SpanLength {
+35 -31
View File
@@ -1,35 +1,39 @@
macro_rules! twoway {
($val:literal => $name:ident) => {
#[test]
fn $name() {
let src = include_str!(concat!("./samples/", $val, ".rsx"));
let formatted = dioxus_autofmt::fmt_file(src);
let out = dioxus_autofmt::apply_formats(src, formatted);
pretty_assertions::assert_eq!(&src, &out);
}
(
$(
// doc attrs
$( #[doc = $doc:expr] )*
$name:ident
),*
) => {
$(
$( #[doc = $doc] )*
#[test]
fn $name() {
let src = include_str!(concat!("./samples/", stringify!($name), ".rsx"));
let formatted = dioxus_autofmt::fmt_file(src);
let out = dioxus_autofmt::apply_formats(src, formatted);
pretty_assertions::assert_eq!(&src, &out);
}
)*
};
}
twoway! ("simple" => simple);
twoway! ("comments" => comments);
twoway! ("attributes" => attributes);
twoway! ("manual_props" => manual_props);
twoway! ("complex" => complex);
twoway! ("tiny" => tiny);
twoway! ("tinynoopt" => tinynoopt);
twoway! ("long" => long);
twoway! ("key" => key);
// Disabled because we can't handle comments on exprs yet
twoway! ("multirsx" => multirsx);
// Disabled because we can't handle comments on exprs yet
twoway! ("commentshard" => commentshard);
twoway![
simple,
comments,
attributes,
manual_props,
complex,
tiny,
tinynoopt,
long,
key,
multirsx,
commentshard,
emoji,
messy_indent,
long_exprs,
ifchain_forloop
];
+5
View File
@@ -0,0 +1,5 @@
rsx! {
div { class: "asdasdasd asdasdasd asdasdasd asdasdasd asdasdasd asdasdasd asdasdasd asdasdasd asdasdasd",
section { "🦀🦀🦀🦀🦀🦀🦀🦀🦀🦀" }
}
}
@@ -0,0 +1,13 @@
rsx! {
// Does this work?
for i in b {
// Hey it works?
div {}
}
// Some ifchain
if a > 10 {
//
rsx! { div {} }
}
}
@@ -0,0 +1,17 @@
rsx! {
div {
div {
div {
div {
section { class: "body-font overflow-hidden dark:bg-ideblack",
div { class: "container px-6 mx-auto",
div { class: "-my-8 divide-y-2 divide-gray-100",
POSTS.iter().enumerate().map(|(id, post)| rsx! { BlogPostItem { post: post, id: id } })
}
}
}
}
}
}
}
}
@@ -0,0 +1,13 @@
fn SaveClipboard(cx: Scope) -> Element {
rsx! {
div { class: "relative w-1/2 {align} max-w-md leading-8",
h2 { class: "mb-6 text-3xl leading-tight md:text-4xl md:leading-tight lg:text-3xl lg:leading-tight font-heading font-mono font-bold",
"{title}"
}
}
};
cx.render(rsx! {
div { "hello world", "hello world", "hello world" }
})
}
+3 -1
View File
@@ -37,7 +37,9 @@ rsx! {
// One level compression
div {
a { class: "py-2 px-3 bg-indigo-500 hover:bg-indigo-600 rounded text-xs text-white", href: "#", "Send invitation" }
a { class: "py-2 px-3 bg-indigo-500 hover:bg-indigo-600 rounded text-xs text-white", href: "#",
"Send invitation"
}
}
// Components
+1
View File
@@ -41,6 +41,7 @@ serde = { version = "1", features = ["derive"], optional = true }
tokio = { version = "1", features = ["full"] }
dioxus = { path = "../dioxus" }
pretty_assertions = "1.3.0"
rand = "0.8.5"
[features]
default = []
+305
View File
@@ -0,0 +1,305 @@
use dioxus::prelude::Props;
use dioxus_core::*;
use std::cell::Cell;
fn random_ns() -> Option<&'static str> {
let namespace = rand::random::<u8>() % 2;
match namespace {
0 => None,
1 => Some(Box::leak(
format!("ns{}", rand::random::<usize>()).into_boxed_str(),
)),
_ => unreachable!(),
}
}
fn create_random_attribute(attr_idx: &mut usize) -> TemplateAttribute<'static> {
match rand::random::<u8>() % 2 {
0 => TemplateAttribute::Static {
name: Box::leak(format!("attr{}", rand::random::<usize>()).into_boxed_str()),
value: Box::leak(format!("value{}", rand::random::<usize>()).into_boxed_str()),
namespace: random_ns(),
},
1 => TemplateAttribute::Dynamic {
id: {
let old_idx = *attr_idx;
*attr_idx += 1;
old_idx
},
},
_ => unreachable!(),
}
}
fn create_random_template_node(
dynamic_node_types: &mut Vec<DynamicNodeType>,
template_idx: &mut usize,
attr_idx: &mut usize,
depth: usize,
) -> TemplateNode<'static> {
match rand::random::<u8>() % 4 {
0 => {
let attrs = {
let attrs: Vec<_> = (0..(rand::random::<usize>() % 10))
.map(|_| create_random_attribute(attr_idx))
.collect();
Box::leak(attrs.into_boxed_slice())
};
TemplateNode::Element {
tag: Box::leak(format!("tag{}", rand::random::<usize>()).into_boxed_str()),
namespace: random_ns(),
attrs,
children: {
if depth > 4 {
&[]
} else {
let children: Vec<_> = (0..(rand::random::<usize>() % 3))
.map(|_| {
create_random_template_node(
dynamic_node_types,
template_idx,
attr_idx,
depth + 1,
)
})
.collect();
Box::leak(children.into_boxed_slice())
}
},
}
}
1 => TemplateNode::Text {
text: Box::leak(format!("{}", rand::random::<usize>()).into_boxed_str()),
},
2 => TemplateNode::DynamicText {
id: {
let old_idx = *template_idx;
*template_idx += 1;
dynamic_node_types.push(DynamicNodeType::Text);
old_idx
},
},
3 => TemplateNode::Dynamic {
id: {
let old_idx = *template_idx;
*template_idx += 1;
dynamic_node_types.push(DynamicNodeType::Other);
old_idx
},
},
_ => unreachable!(),
}
}
fn generate_paths(
node: &TemplateNode<'static>,
current_path: &[u8],
node_paths: &mut Vec<Vec<u8>>,
attr_paths: &mut Vec<Vec<u8>>,
) {
match node {
TemplateNode::Element { children, attrs, .. } => {
for attr in *attrs {
match attr {
TemplateAttribute::Static { .. } => {}
TemplateAttribute::Dynamic { .. } => {
attr_paths.push(current_path.to_vec());
}
}
}
for (i, child) in children.iter().enumerate() {
let mut current_path = current_path.to_vec();
current_path.push(i as u8);
generate_paths(child, &current_path, node_paths, attr_paths);
}
}
TemplateNode::Text { .. } => {}
TemplateNode::DynamicText { .. } => {
node_paths.push(current_path.to_vec());
}
TemplateNode::Dynamic { .. } => {
node_paths.push(current_path.to_vec());
}
}
}
enum DynamicNodeType {
Text,
Other,
}
fn create_random_template(name: &'static str) -> (Template<'static>, Vec<DynamicNodeType>) {
let mut dynamic_node_type = Vec::new();
let mut template_idx = 0;
let mut attr_idx = 0;
let roots = (0..(1 + rand::random::<usize>() % 5))
.map(|_| {
create_random_template_node(&mut dynamic_node_type, &mut template_idx, &mut attr_idx, 0)
})
.collect::<Vec<_>>();
assert!(!roots.is_empty());
let roots = Box::leak(roots.into_boxed_slice());
let mut node_paths = Vec::new();
let mut attr_paths = Vec::new();
for (i, root) in roots.iter().enumerate() {
generate_paths(root, &[i as u8], &mut node_paths, &mut attr_paths);
}
let node_paths = Box::leak(
node_paths
.into_iter()
.map(|v| &*Box::leak(v.into_boxed_slice()))
.collect::<Vec<_>>()
.into_boxed_slice(),
);
let attr_paths = Box::leak(
attr_paths
.into_iter()
.map(|v| &*Box::leak(v.into_boxed_slice()))
.collect::<Vec<_>>()
.into_boxed_slice(),
);
(
Template { name, roots, node_paths, attr_paths },
dynamic_node_type,
)
}
fn create_random_dynamic_node(cx: &ScopeState, depth: usize) -> DynamicNode {
let range = if depth > 3 { 1 } else { 3 };
match rand::random::<u8>() % range {
0 => DynamicNode::Placeholder(Default::default()),
1 => cx.make_node((0..(rand::random::<u8>() % 5)).map(|_| VNode {
key: None,
parent: Default::default(),
template: Cell::new(Template {
name: concat!(file!(), ":", line!(), ":", column!(), ":0"),
roots: &[TemplateNode::Dynamic { id: 0 }],
node_paths: &[&[0]],
attr_paths: &[],
}),
root_ids: Default::default(),
dynamic_nodes: cx.bump().alloc([cx.component(
create_random_element,
DepthProps { depth, root: false },
"create_random_element",
)]),
dynamic_attrs: &[],
})),
2 => cx.component(
create_random_element,
DepthProps { depth, root: false },
"create_random_element",
),
_ => unreachable!(),
}
}
fn create_random_dynamic_attr(cx: &ScopeState) -> Attribute {
let value = match rand::random::<u8>() % 6 {
0 => AttributeValue::Text(Box::leak(
format!("{}", rand::random::<usize>()).into_boxed_str(),
)),
1 => AttributeValue::Float(rand::random()),
2 => AttributeValue::Int(rand::random()),
3 => AttributeValue::Bool(rand::random()),
4 => cx.any_value(rand::random::<usize>()),
5 => AttributeValue::None,
// Listener(RefCell<Option<ListenerCb<'a>>>),
_ => unreachable!(),
};
Attribute {
name: Box::leak(format!("attr{}", rand::random::<usize>()).into_boxed_str()),
value,
namespace: random_ns(),
mounted_element: Default::default(),
volatile: rand::random(),
}
}
static mut TEMPLATE_COUNT: usize = 0;
#[derive(PartialEq, Props)]
struct DepthProps {
depth: usize,
root: bool,
}
fn create_random_element(cx: Scope<DepthProps>) -> Element {
cx.needs_update();
let range = if cx.props.root { 2 } else { 3 };
let node = match rand::random::<usize>() % range {
0 | 1 => {
let (template, dynamic_node_types) = create_random_template(Box::leak(
format!(
"{}{}",
concat!(file!(), ":", line!(), ":", column!(), ":"),
{
unsafe {
let old = TEMPLATE_COUNT;
TEMPLATE_COUNT += 1;
old
}
}
)
.into_boxed_str(),
));
println!("{template:#?}");
let node = VNode {
key: None,
parent: None,
template: Cell::new(template),
root_ids: Default::default(),
dynamic_nodes: {
let dynamic_nodes: Vec<_> = dynamic_node_types
.iter()
.map(|ty| match ty {
DynamicNodeType::Text => DynamicNode::Text(VText {
value: Box::leak(
format!("{}", rand::random::<usize>()).into_boxed_str(),
),
id: Default::default(),
}),
DynamicNodeType::Other => {
create_random_dynamic_node(cx, cx.props.depth + 1)
}
})
.collect();
cx.bump().alloc(dynamic_nodes)
},
dynamic_attrs: cx.bump().alloc(
(0..template.attr_paths.len())
.map(|_| create_random_dynamic_attr(cx))
.collect::<Vec<_>>(),
),
};
Some(node)
}
_ => None,
};
println!("{node:#?}");
node
}
// test for panics when creating random nodes and templates
#[test]
fn create() {
for _ in 0..100 {
let mut vdom =
VirtualDom::new_with_props(create_random_element, DepthProps { depth: 0, root: true });
let _ = vdom.rebuild();
}
}
// test for panics when diffing random nodes
// This test will change the template every render which is not very realistic, but it helps stress the system
#[test]
fn diff() {
for _ in 0..10 {
let mut vdom =
VirtualDom::new_with_props(create_random_element, DepthProps { depth: 0, root: true });
let _ = vdom.rebuild();
for _ in 0..10 {
let _ = vdom.render_immediate();
}
}
}
@@ -129,6 +129,34 @@ mod js {
root.appendChild(els[k]);
}
}
const bool_attrs = {
allowfullscreen: true,
allowpaymentrequest: true,
async: true,
autofocus: true,
autoplay: true,
checked: true,
controls: true,
default: true,
defer: true,
disabled: true,
formnovalidate: true,
hidden: true,
ismap: true,
itemscope: true,
loop: true,
multiple: true,
muted: true,
nomodule: true,
novalidate: true,
open: true,
playsinline: true,
readonly: true,
required: true,
reversed: true,
selected: true,
truespeed: true,
};
"#;
extern "C" {
+8 -15
View File
@@ -13,37 +13,29 @@ license = "MIT/Apache-2.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
dioxus-html = { path = "../html", features = ["serialize"], version = "^0.3.0" }
dioxus-core = { path = "../core", features = ["serialize"], version = "^0.3.0" }
dioxus-interpreter-js = { path = "../interpreter", version = "0.3.0" }
dioxus-hot-reload = { path = "../hot-reload" }
tokio = { version = "1.23.0", features = ["full"] }
thiserror = "1.0.38"
futures-util = { version = "0.3.25", default-features = false, features = [
"sink",
] }
futures-channel = { version = "0.3.25", features = ["sink"] }
pretty_env_logger = "0.4.0"
tokio = { version = "1.23.0", features = ["time"] }
tokio-stream = { version = "0.1.11", features = ["net"] }
tokio-util = { version = "0.7.4", features = ["rt"] }
serde = { version = "1.0.151", features = ["derive"] }
serde_json = "1.0.91"
tokio-util = { version = "0.7.4", features = ["full"] }
interprocess = { version = "1.2.1", optional = true }
dioxus-html = { path = "../html", features = ["serialize"], version = "^0.3.0" }
dioxus-core = { path = "../core", features = ["serialize"], version = "^0.3.0" }
dioxus-interpreter-js = { path = "../interpreter", version = "0.3.0" }
dioxus-hot-reload = { path = "../hot-reload" }
# warp
warp = { version = "0.3.3", optional = true }
# axum
axum = { version = "0.6.1", optional = true, features = ["ws"] }
tower = { version = "0.4.13", optional = true }
# salvo
salvo = { version = "0.37.7", optional = true, features = ["ws"] }
thiserror = "1.0.38"
uuid = { version = "1.2.2", features = ["v4"] }
anyhow = "1.0.68"
# actix is ... complicated?
# actix-files = { version = "0.6.2", optional = true }
@@ -51,6 +43,7 @@ anyhow = "1.0.68"
# actix-ws = { version = "0.2.5", optional = true }
[dev-dependencies]
pretty_env_logger = { version = "0.4.0" }
tokio = { version = "1.23.0", features = ["full"] }
dioxus = { path = "../dioxus", version = "0.3.0" }
warp = "0.3.3"
+31 -19
View File
@@ -95,25 +95,7 @@ impl Parse for BodyNode {
// Transform for loops into into_iter calls
if stream.peek(Token![for]) {
let _f = stream.parse::<Token![for]>()?;
let pat = stream.parse::<Pat>()?;
let _i = stream.parse::<Token![in]>()?;
let expr = stream.parse::<Box<Expr>>()?;
let body;
braced!(body in stream);
let mut children = vec![];
while !body.is_empty() {
children.push(body.parse()?);
}
return Ok(BodyNode::ForLoop(ForLoop {
for_token: _f,
pat,
in_token: _i,
expr,
body: children,
}));
return Ok(BodyNode::ForLoop(stream.parse()?));
}
// Transform unterminated if statements into terminated optional if statements
@@ -221,6 +203,36 @@ pub struct ForLoop {
pub in_token: Token![in],
pub expr: Box<Expr>,
pub body: Vec<BodyNode>,
pub brace_token: token::Brace,
}
impl Parse for ForLoop {
fn parse(input: ParseStream) -> Result<Self> {
let for_token: Token![for] = input.parse()?;
let pat = input.parse()?;
let in_token: Token![in] = input.parse()?;
let expr: Expr = input.call(Expr::parse_without_eager_brace)?;
let content;
let brace_token = braced!(content in input);
let mut children = vec![];
while !content.is_empty() {
children.push(content.parse()?);
}
Ok(Self {
for_token,
pat,
in_token,
body: children,
expr: Box::new(expr),
brace_token,
})
}
}
fn is_if_chain_terminated(chain: &ExprIf) -> bool {