mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-11 15:07:08 +00:00
wip: more cleanup, more tests
This commit is contained in:
parent
a2a194ca40
commit
d70b436157
10 changed files with 560 additions and 423 deletions
|
@ -1,79 +1,8 @@
|
|||
use crate::{util::*, write_ident};
|
||||
use crate::{util::*, FormattedBlock};
|
||||
use dioxus_rsx::*;
|
||||
use std::fmt::Write;
|
||||
use triple_accel::{levenshtein_search, Match};
|
||||
|
||||
#[derive(serde::Deserialize, serde::Serialize, Clone, Debug, PartialEq, Hash)]
|
||||
pub struct FormattedBlock {
|
||||
pub formatted: String,
|
||||
pub start: usize,
|
||||
pub end: usize,
|
||||
}
|
||||
|
||||
pub fn get_format_blocks(contents: &str) -> Vec<FormattedBlock> {
|
||||
let matches = levenshtein_search(b"rsx! {", contents.as_bytes()).peekable();
|
||||
|
||||
let mut formatted_blocks = Vec::new();
|
||||
let mut last_bracket_end = 0;
|
||||
|
||||
// find the rsx! marker
|
||||
for Match { start, end, k } in matches {
|
||||
// ensure the marker is not nested
|
||||
if start < last_bracket_end {
|
||||
continue;
|
||||
}
|
||||
|
||||
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 new = fmt_block(sub_string).unwrap();
|
||||
|
||||
let stripped = &contents[end + 1..bracket_end + end - 1];
|
||||
|
||||
if stripped == new {
|
||||
continue;
|
||||
}
|
||||
|
||||
// if we have code to push, we want the code to end up on the right lines with the right indentation
|
||||
let mut output = String::new();
|
||||
writeln!(output).unwrap();
|
||||
|
||||
for line in new.lines() {
|
||||
writeln!(output, "{}", line).ok();
|
||||
}
|
||||
|
||||
formatted_blocks.push(FormattedBlock {
|
||||
formatted: output,
|
||||
start: end,
|
||||
end: end + bracket_end - 1,
|
||||
});
|
||||
}
|
||||
|
||||
formatted_blocks
|
||||
}
|
||||
|
||||
struct Isolate<'a> {
|
||||
contents: &'a str,
|
||||
start: usize,
|
||||
end: usize,
|
||||
}
|
||||
fn isolate_body_of_rsx(contents: &str, Match { start, end, k }: triple_accel::Match) -> Isolate {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub fn fmt_block(block: &str) -> Option<String> {
|
||||
let mut buf = String::new();
|
||||
let lines = block.split('\n').collect::<Vec<_>>();
|
||||
|
||||
for node in &syn::parse_str::<CallBody>(block).ok()?.roots {
|
||||
write_ident(&mut buf, &lines, node, 0).ok()?;
|
||||
}
|
||||
|
||||
Some(buf)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn format_block_basic() {
|
||||
let block = r#"
|
||||
|
@ -129,7 +58,7 @@ fn format_block_basic() {
|
|||
|
||||
// div { class: "asdasd", p { "hello!" } }
|
||||
|
||||
let edits = get_format_blocks(block);
|
||||
// let edits = get_format_blocks(block);
|
||||
|
||||
println!("{}", edits[0].formatted);
|
||||
// println!("{}", edits[0].formatted);
|
||||
}
|
||||
|
|
|
@ -1,17 +1,83 @@
|
|||
use std::fmt::Result;
|
||||
use std::fmt::{Result, Write};
|
||||
|
||||
struct Buffer {
|
||||
buf: String,
|
||||
cur_line: usize,
|
||||
cur_indent: usize,
|
||||
use dioxus_rsx::BodyNode;
|
||||
|
||||
pub struct Buffer {
|
||||
pub buf: String,
|
||||
pub line: usize,
|
||||
pub indent: usize,
|
||||
}
|
||||
|
||||
impl Buffer {
|
||||
fn write_tabs(&mut self, num: usize) -> Result {
|
||||
todo!()
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
buf: String::new(),
|
||||
line: 0,
|
||||
indent: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn new_line(&mut self) -> Result {
|
||||
todo!()
|
||||
// Create a new line and tab it to the current tab level
|
||||
pub fn tabbed_line(&mut self) -> Result {
|
||||
self.new_line()?;
|
||||
self.tab()
|
||||
}
|
||||
|
||||
// Create a new line and tab it to the current tab level
|
||||
pub fn indented_tabbed_line(&mut self) -> Result {
|
||||
self.new_line()?;
|
||||
self.indented_tab()
|
||||
}
|
||||
|
||||
pub fn tab(&mut self) -> Result {
|
||||
self.write_tabs(self.indent)
|
||||
}
|
||||
|
||||
pub fn indented_tab(&mut self) -> Result {
|
||||
self.write_tabs(self.indent + 1)
|
||||
}
|
||||
|
||||
pub fn write_tabs(&mut self, num: usize) -> std::fmt::Result {
|
||||
for _ in 0..num {
|
||||
write!(self.buf, " ")?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn new_line(&mut self) -> Result {
|
||||
writeln!(self.buf)
|
||||
}
|
||||
|
||||
pub fn write_indented_ident(&mut self, lines: &[&str], node: &BodyNode) -> Result {
|
||||
self.write_ident(lines, node)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_ident(&mut self, lines: &[&str], node: &BodyNode) -> Result {
|
||||
match node {
|
||||
BodyNode::Element(el) => self.write_element(el, lines),
|
||||
BodyNode::Component(component) => self.write_component(component, lines),
|
||||
BodyNode::Text(text) => self.write_text(text),
|
||||
BodyNode::RawExpr(exp) => self.write_raw_expr(exp, lines),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_text(&mut self, text: &syn::LitStr) -> Result {
|
||||
write!(self.buf, "\"{}\"", text.value())
|
||||
}
|
||||
|
||||
// Push out the indent level and write each component, line by line
|
||||
pub fn write_body_indented(&mut self, children: &[BodyNode], lines: &[&str]) -> Result {
|
||||
self.indent += 1;
|
||||
for child in children {
|
||||
// Exprs handle their own indenting/line breaks
|
||||
if !matches!(child, BodyNode::RawExpr(_)) {
|
||||
self.tabbed_line()?;
|
||||
}
|
||||
|
||||
self.write_ident(lines, child)?;
|
||||
}
|
||||
self.indent -= 1;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,78 +1,85 @@
|
|||
use crate::{util::*, write_ident};
|
||||
use crate::Buffer;
|
||||
use dioxus_rsx::*;
|
||||
use quote::ToTokens;
|
||||
use std::fmt::{self, Write};
|
||||
use std::fmt::{self, Result, Write};
|
||||
|
||||
pub fn write_component(
|
||||
component: &Component,
|
||||
buf: &mut String,
|
||||
indent: usize,
|
||||
lines: &[&str],
|
||||
) -> Result<(), fmt::Error> {
|
||||
let Component {
|
||||
name,
|
||||
body,
|
||||
children,
|
||||
manual_props,
|
||||
prop_gen_args,
|
||||
} = component;
|
||||
let mut name = name.to_token_stream().to_string();
|
||||
name.retain(|c| !c.is_whitespace());
|
||||
write_tabs(buf, indent)?;
|
||||
write!(buf, "{name}")?;
|
||||
if let Some(generics) = prop_gen_args {
|
||||
let mut written = generics.to_token_stream().to_string();
|
||||
written.retain(|c| !c.is_whitespace());
|
||||
write!(buf, "{}", written)?;
|
||||
}
|
||||
write!(buf, " {{")?;
|
||||
if !body.is_empty() || !children.is_empty() {
|
||||
writeln!(buf)?;
|
||||
}
|
||||
for field in body {
|
||||
write_tabs(buf, indent + 1)?;
|
||||
let name = &field.name;
|
||||
match &field.content {
|
||||
ContentField::ManExpr(exp) => {
|
||||
let out = prettyplease::unparse_expr(exp);
|
||||
writeln!(buf, "{}: {},", name, out)?;
|
||||
}
|
||||
ContentField::Formatted(s) => {
|
||||
writeln!(buf, "{}: \"{}\",", name, s.value())?;
|
||||
}
|
||||
ContentField::OnHandlerRaw(exp) => {
|
||||
let out = prettyplease::unparse_expr(exp);
|
||||
let mut lines = out.split('\n').peekable();
|
||||
let first = lines.next().unwrap();
|
||||
write!(buf, "{}: {}", name, first)?;
|
||||
for line in lines {
|
||||
writeln!(buf)?;
|
||||
write_tabs(buf, indent + 1)?;
|
||||
write!(buf, "{}", line)?;
|
||||
impl Buffer {
|
||||
pub fn write_component(
|
||||
&mut self,
|
||||
Component {
|
||||
name,
|
||||
body,
|
||||
children,
|
||||
manual_props,
|
||||
prop_gen_args,
|
||||
}: &Component,
|
||||
lines: &[&str],
|
||||
) -> Result {
|
||||
let mut name = name.to_token_stream().to_string();
|
||||
name.retain(|c| !c.is_whitespace());
|
||||
self.tab()?;
|
||||
write!(self.buf, "{name}")?;
|
||||
|
||||
if let Some(generics) = prop_gen_args {
|
||||
let mut written = generics.to_token_stream().to_string();
|
||||
written.retain(|c| !c.is_whitespace());
|
||||
write!(self.buf, "{}", written)?;
|
||||
}
|
||||
|
||||
write!(self.buf, " {{")?;
|
||||
|
||||
if !body.is_empty() || !children.is_empty() {
|
||||
self.new_line()?;
|
||||
}
|
||||
|
||||
for field in body {
|
||||
self.indented_tab()?;
|
||||
let name = &field.name;
|
||||
match &field.content {
|
||||
ContentField::ManExpr(exp) => {
|
||||
let out = prettyplease::unparse_expr(exp);
|
||||
writeln!(self.buf, "{}: {},", name, out)?;
|
||||
}
|
||||
ContentField::Formatted(s) => {
|
||||
writeln!(self.buf, "{}: \"{}\",", name, s.value())?;
|
||||
}
|
||||
ContentField::OnHandlerRaw(exp) => {
|
||||
let out = prettyplease::unparse_expr(exp);
|
||||
let mut lines = out.split('\n').peekable();
|
||||
let first = lines.next().unwrap();
|
||||
write!(self.buf, "{}: {}", name, first)?;
|
||||
for line in lines {
|
||||
self.new_line()?;
|
||||
self.indented_tab()?;
|
||||
write!(self.buf, "{}", line)?;
|
||||
}
|
||||
writeln!(self.buf, ",")?;
|
||||
}
|
||||
writeln!(buf, ",")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(exp) = manual_props {
|
||||
write_tabs(buf, indent + 1)?;
|
||||
let out = prettyplease::unparse_expr(exp);
|
||||
let mut lines = out.split('\n').peekable();
|
||||
let first = lines.next().unwrap();
|
||||
write!(buf, "..{}", first)?;
|
||||
for line in lines {
|
||||
writeln!(buf)?;
|
||||
write_tabs(buf, indent + 1)?;
|
||||
write!(buf, "{}", line)?;
|
||||
|
||||
if let Some(exp) = manual_props {
|
||||
self.indented_tab()?;
|
||||
let out = prettyplease::unparse_expr(exp);
|
||||
let mut lines = out.split('\n').peekable();
|
||||
let first = lines.next().unwrap();
|
||||
write!(self.buf, "..{}", first)?;
|
||||
for line in lines {
|
||||
self.new_line()?;
|
||||
self.indented_tab()?;
|
||||
write!(self.buf, "{}", line)?;
|
||||
}
|
||||
self.new_line()?;
|
||||
}
|
||||
writeln!(buf)?;
|
||||
|
||||
for child in children {
|
||||
self.write_indented_ident(lines, child)?;
|
||||
}
|
||||
|
||||
if !body.is_empty() || !children.is_empty() {
|
||||
self.tab()?;
|
||||
}
|
||||
writeln!(self.buf, "}}")?;
|
||||
Ok(())
|
||||
}
|
||||
for child in children {
|
||||
write_ident(buf, lines, child, indent + 1)?;
|
||||
}
|
||||
if !body.is_empty() || !children.is_empty() {
|
||||
write_tabs(buf, indent)?;
|
||||
}
|
||||
writeln!(buf, "}}")?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{util::*, write_ident};
|
||||
use crate::{util::*, Buffer};
|
||||
use dioxus_rsx::*;
|
||||
use std::{fmt, fmt::Result, fmt::Write};
|
||||
use std::{fmt::Result, fmt::Write};
|
||||
|
||||
enum ShortOptimization {
|
||||
// Special because we want to print the closing bracket immediately
|
||||
|
@ -16,108 +16,181 @@ enum ShortOptimization {
|
|||
NoOpt,
|
||||
}
|
||||
|
||||
pub fn write_element(
|
||||
Element {
|
||||
name,
|
||||
key,
|
||||
attributes,
|
||||
children,
|
||||
_is_static,
|
||||
}: &Element,
|
||||
buf: &mut String,
|
||||
lines: &[&str],
|
||||
indent: usize,
|
||||
) -> Result {
|
||||
/*
|
||||
1. Write the tag
|
||||
2. Write the key
|
||||
3. Write the attributes
|
||||
4. Write the children
|
||||
*/
|
||||
impl Buffer {
|
||||
pub fn write_element(
|
||||
&mut self,
|
||||
Element {
|
||||
name,
|
||||
key,
|
||||
attributes,
|
||||
children,
|
||||
_is_static,
|
||||
}: &Element,
|
||||
lines: &[&str],
|
||||
) -> Result {
|
||||
/*
|
||||
1. Write the tag
|
||||
2. Write the key
|
||||
3. Write the attributes
|
||||
4. Write the children
|
||||
*/
|
||||
|
||||
write!(buf, "{name} {{")?;
|
||||
write!(self.buf, "{name} {{")?;
|
||||
|
||||
// decide if we have any special optimizations
|
||||
// Default with none, opt the cases in one-by-one
|
||||
let mut opt_level = ShortOptimization::NoOpt;
|
||||
// decide if we have any special optimizations
|
||||
// Default with none, opt the cases in one-by-one
|
||||
let mut opt_level = ShortOptimization::NoOpt;
|
||||
|
||||
// check if we have a lot of attributes
|
||||
let is_short_attr_list = is_short_attrs(attributes);
|
||||
let is_small_children = is_short_children(children);
|
||||
// check if we have a lot of attributes
|
||||
let is_short_attr_list = is_short_attrs(attributes);
|
||||
let is_small_children = is_short_children(children);
|
||||
|
||||
// if we have few attributes and a lot of children, place the attrs on top
|
||||
if is_short_attr_list && !is_small_children {
|
||||
opt_level = ShortOptimization::PropsOnTop;
|
||||
}
|
||||
|
||||
// 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 there's nothing at all, empty optimization
|
||||
if attributes.is_empty() && children.is_empty() && key.is_none() {
|
||||
opt_level = ShortOptimization::Empty;
|
||||
}
|
||||
|
||||
match opt_level {
|
||||
ShortOptimization::Empty => write!(buf, "}}")?,
|
||||
ShortOptimization::Oneliner => {
|
||||
write!(buf, " ")?;
|
||||
write_attributes(buf, attributes, true, indent)?;
|
||||
|
||||
if !children.is_empty() && !attributes.is_empty() {
|
||||
write!(buf, ", ")?;
|
||||
}
|
||||
|
||||
// write the children
|
||||
for child in children {
|
||||
write_ident(buf, lines, child, indent + 1)?;
|
||||
}
|
||||
|
||||
write!(buf, " }}")?;
|
||||
// if we have few attributes and a lot of children, place the attrs on top
|
||||
if is_short_attr_list && !is_small_children {
|
||||
opt_level = ShortOptimization::PropsOnTop;
|
||||
}
|
||||
|
||||
ShortOptimization::PropsOnTop => {
|
||||
write!(buf, " ")?;
|
||||
write_attributes(buf, attributes, true, indent)?;
|
||||
|
||||
if !children.is_empty() && !attributes.is_empty() {
|
||||
write!(buf, ",")?;
|
||||
// even if the attr is long, it should be put on one line
|
||||
if !is_short_attr_list && attributes.len() <= 1 {
|
||||
if children.is_empty() {
|
||||
opt_level = ShortOptimization::Oneliner;
|
||||
} else {
|
||||
opt_level = ShortOptimization::PropsOnTop;
|
||||
}
|
||||
|
||||
// write the children
|
||||
for child in children {
|
||||
writeln!(buf)?;
|
||||
write_tabs(buf, indent + 1)?;
|
||||
write_ident(buf, lines, child, indent + 1)?;
|
||||
}
|
||||
|
||||
writeln!(buf)?;
|
||||
write_tabs(buf, indent)?;
|
||||
write!(buf, "}}")?;
|
||||
}
|
||||
|
||||
ShortOptimization::NoOpt => {
|
||||
// write the key
|
||||
// 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;
|
||||
}
|
||||
|
||||
// write the attributes
|
||||
write_attributes(buf, attributes, false, indent)?;
|
||||
// If there's nothing at all, empty optimization
|
||||
if attributes.is_empty() && children.is_empty() && key.is_none() {
|
||||
opt_level = ShortOptimization::Empty;
|
||||
}
|
||||
|
||||
// write the children
|
||||
for child in children {
|
||||
writeln!(buf)?;
|
||||
write_tabs(buf, indent + 1)?;
|
||||
write_ident(buf, lines, child, indent + 1)?;
|
||||
match opt_level {
|
||||
ShortOptimization::Empty => write!(self.buf, "}}")?,
|
||||
ShortOptimization::Oneliner => {
|
||||
write!(self.buf, " ")?;
|
||||
self.write_attributes(attributes, true)?;
|
||||
|
||||
if !children.is_empty() && !attributes.is_empty() {
|
||||
write!(self.buf, ", ")?;
|
||||
}
|
||||
|
||||
// write the children
|
||||
for child in children {
|
||||
self.write_ident(lines, child)?;
|
||||
}
|
||||
|
||||
write!(self.buf, " }}")?;
|
||||
}
|
||||
|
||||
writeln!(buf)?;
|
||||
write_tabs(buf, indent)?;
|
||||
write!(buf, "}}")?;
|
||||
ShortOptimization::PropsOnTop => {
|
||||
write!(self.buf, " ")?;
|
||||
self.write_attributes(attributes, true)?;
|
||||
|
||||
if !children.is_empty() && !attributes.is_empty() {
|
||||
write!(self.buf, ",")?;
|
||||
}
|
||||
|
||||
// write the children
|
||||
self.write_body_indented(children, lines)?;
|
||||
|
||||
self.tabbed_line()?;
|
||||
write!(self.buf, "}}")?;
|
||||
}
|
||||
|
||||
ShortOptimization::NoOpt => {
|
||||
// write the key
|
||||
|
||||
// write the attributes
|
||||
self.write_attributes(attributes, false)?;
|
||||
|
||||
self.write_body_indented(children, lines)?;
|
||||
|
||||
self.tabbed_line()?;
|
||||
write!(self.buf, "}}")?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Ok(())
|
||||
fn write_attributes(&mut self, attributes: &[ElementAttrNamed], sameline: bool) -> Result {
|
||||
let mut attr_iter = attributes.iter().peekable();
|
||||
|
||||
while let Some(attr) = attr_iter.next() {
|
||||
if !sameline {
|
||||
self.indented_tabbed_line()?;
|
||||
}
|
||||
self.write_attribute(attr)?;
|
||||
|
||||
if attr_iter.peek().is_some() {
|
||||
write!(self.buf, ",")?;
|
||||
|
||||
if sameline {
|
||||
write!(self.buf, " ")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_attribute(&mut self, attr: &ElementAttrNamed) -> Result {
|
||||
match &attr.attr {
|
||||
ElementAttr::AttrText { name, value } => {
|
||||
write!(self.buf, "{name}: \"{value}\"", value = value.value())?;
|
||||
}
|
||||
ElementAttr::AttrExpression { name, value } => {
|
||||
let out = prettyplease::unparse_expr(value);
|
||||
write!(self.buf, "{}: {}", name, out)?;
|
||||
}
|
||||
|
||||
ElementAttr::CustomAttrText { name, value } => {
|
||||
write!(
|
||||
self.buf,
|
||||
"\"{name}\": \"{value}\"",
|
||||
name = name.value(),
|
||||
value = value.value()
|
||||
)?;
|
||||
}
|
||||
|
||||
ElementAttr::CustomAttrExpression { name, value } => {
|
||||
let out = prettyplease::unparse_expr(value);
|
||||
write!(self.buf, "\"{}\": {}", name.value(), out)?;
|
||||
}
|
||||
|
||||
ElementAttr::EventTokens { name, tokens } => {
|
||||
let out = prettyplease::unparse_expr(tokens);
|
||||
|
||||
let mut lines = out.split('\n').peekable();
|
||||
let first = lines.next().unwrap();
|
||||
|
||||
// a one-liner for whatever reason
|
||||
// Does not need a new line
|
||||
if lines.peek().is_none() {
|
||||
write!(self.buf, "{}: {}", name, first)?;
|
||||
} else {
|
||||
writeln!(self.buf, "{}: {}", name, first)?;
|
||||
|
||||
while let Some(line) = lines.next() {
|
||||
self.indented_tab()?;
|
||||
write!(self.buf, "{}", line)?;
|
||||
if lines.peek().is_none() {
|
||||
write!(self.buf, "")?;
|
||||
} else {
|
||||
writeln!(self.buf)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn is_short_attrs(attrs: &[ElementAttrNamed]) -> bool {
|
||||
|
@ -132,106 +205,29 @@ fn is_short_children(children: &[BodyNode]) -> bool {
|
|||
return true;
|
||||
}
|
||||
|
||||
if children.len() == 1 {
|
||||
if let BodyNode::Text(ref text) = &children[0] {
|
||||
return text.value().len() < 80;
|
||||
}
|
||||
}
|
||||
match children {
|
||||
[BodyNode::Text(ref text)] => text.value().len() < 80,
|
||||
[BodyNode::Element(ref el)] => {
|
||||
// && !el.attributes.iter().any(|f| f.attr.is_expr())
|
||||
|
||||
false
|
||||
extract_attr_len(&el.attributes) < 80 && is_short_children(&el.children)
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn write_key() {
|
||||
// if let Some(key) = key.as_ref().map(|f| f.value()) {
|
||||
// if is_long_attr_list {
|
||||
// writeln!(buf)?;
|
||||
// write_tabs(buf, indent + 1)?;
|
||||
// self.new_line()?;
|
||||
// self.write_tabs( indent + 1)?;
|
||||
// } else {
|
||||
// write!(buf, " ")?;
|
||||
// write!(self.buf, " ")?;
|
||||
// }
|
||||
// write!(buf, "key: \"{key}\"")?;
|
||||
// write!(self.buf, "key: \"{key}\"")?;
|
||||
|
||||
// if !attributes.is_empty() {
|
||||
// write!(buf, ",")?;
|
||||
// write!(self.buf, ",")?;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
fn write_attributes(
|
||||
buf: &mut String,
|
||||
attributes: &[ElementAttrNamed],
|
||||
sameline: bool,
|
||||
indent: usize,
|
||||
) -> Result {
|
||||
let mut attr_iter = attributes.iter().peekable();
|
||||
|
||||
while let Some(attr) = attr_iter.next() {
|
||||
write_attribute(buf, attr, indent)?;
|
||||
|
||||
if attr_iter.peek().is_some() {
|
||||
write!(buf, ",")?;
|
||||
|
||||
if sameline {
|
||||
write!(buf, " ")?;
|
||||
} else {
|
||||
writeln!(buf)?;
|
||||
write_tabs(buf, indent + 1)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_attribute(buf: &mut String, attr: &ElementAttrNamed, indent: usize) -> Result {
|
||||
match &attr.attr {
|
||||
ElementAttr::AttrText { name, value } => {
|
||||
write!(buf, "{name}: \"{value}\"", value = value.value())?;
|
||||
}
|
||||
ElementAttr::AttrExpression { name, value } => {
|
||||
let out = prettyplease::unparse_expr(value);
|
||||
write!(buf, "{}: {}", name, out)?;
|
||||
}
|
||||
|
||||
ElementAttr::CustomAttrText { name, value } => {
|
||||
write!(
|
||||
buf,
|
||||
"\"{name}\": \"{value}\"",
|
||||
name = name.value(),
|
||||
value = value.value()
|
||||
)?;
|
||||
}
|
||||
|
||||
ElementAttr::CustomAttrExpression { name, value } => {
|
||||
let out = prettyplease::unparse_expr(value);
|
||||
write!(buf, "\"{}\": {}", name.value(), out)?;
|
||||
}
|
||||
|
||||
ElementAttr::EventTokens { name, tokens } => {
|
||||
let out = prettyplease::unparse_expr(tokens);
|
||||
|
||||
let mut lines = out.split('\n').peekable();
|
||||
let first = lines.next().unwrap();
|
||||
|
||||
// a one-liner for whatever reason
|
||||
// Does not need a new line
|
||||
if lines.peek().is_none() {
|
||||
write!(buf, "{}: {}", name, first)?;
|
||||
} else {
|
||||
writeln!(buf, "{}: {}", name, first)?;
|
||||
|
||||
while let Some(line) = lines.next() {
|
||||
write_tabs(buf, indent + 1)?;
|
||||
write!(buf, "{}", line)?;
|
||||
if lines.peek().is_none() {
|
||||
write!(buf, "")?;
|
||||
} else {
|
||||
writeln!(buf)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,39 +1,53 @@
|
|||
//! pretty printer for rsx!
|
||||
use std::fmt::{self, Write};
|
||||
use std::fmt::{self, Result, Write};
|
||||
|
||||
pub fn write_raw_expr(
|
||||
exp: &syn::Expr,
|
||||
indent: usize,
|
||||
lines: &[&str],
|
||||
buf: &mut String,
|
||||
) -> Result<(), fmt::Error> {
|
||||
use syn::spanned::Spanned;
|
||||
let placement = exp.span();
|
||||
let start = placement.start();
|
||||
let end = placement.end();
|
||||
let num_spaces_desired = (indent * 4) as isize;
|
||||
let first = lines[start.line - 1];
|
||||
let num_spaces_real = first.chars().take_while(|c| c.is_whitespace()).count() as isize;
|
||||
let offset = num_spaces_real - num_spaces_desired;
|
||||
use crate::Buffer;
|
||||
|
||||
for line_id in start.line - 1..end.line {
|
||||
let line = lines[line_id];
|
||||
impl Buffer {
|
||||
pub fn write_raw_expr(&mut self, exp: &syn::Expr, lines: &[&str]) -> Result {
|
||||
/*
|
||||
We want to normalize the expr to the appropriate indent level.
|
||||
*/
|
||||
|
||||
// trim the leading whitespace
|
||||
// in a perfect world, just fire up the rust pretty printer
|
||||
// pretty_print_rust_code_as_if_it_were_rustfmt()
|
||||
|
||||
if offset < 0 {
|
||||
for _ in 0..-offset {
|
||||
write!(buf, " ")?;
|
||||
use syn::spanned::Spanned;
|
||||
let placement = exp.span();
|
||||
let start = placement.start();
|
||||
let end = placement.end();
|
||||
let num_spaces_desired = (self.indent * 4) as isize;
|
||||
|
||||
let first = lines[start.line - 1];
|
||||
let num_spaces_real = first.chars().take_while(|c| c.is_whitespace()).count() as isize;
|
||||
|
||||
let offset = num_spaces_real - num_spaces_desired;
|
||||
|
||||
for line in &lines[start.line - 1..end.line] {
|
||||
writeln!(self.buf)?;
|
||||
// trim the leading whitespace
|
||||
if offset < 0 {
|
||||
for _ in 0..-offset {
|
||||
write!(self.buf, " ")?;
|
||||
}
|
||||
|
||||
write!(self.buf, "{}", line)?;
|
||||
} else {
|
||||
let offset = offset as usize;
|
||||
let right = &line[offset..];
|
||||
write!(self.buf, "{}", right)?;
|
||||
}
|
||||
|
||||
writeln!(buf, "{}", line)?;
|
||||
} else {
|
||||
let offset = offset as usize;
|
||||
|
||||
let right = &line[offset..];
|
||||
writeln!(buf, "{}", right)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// :(
|
||||
// fn pretty_print_rust_code_as_if_it_were_rustfmt(code: &str) -> String {
|
||||
// let formatted = prettyplease::unparse_expr(exp);
|
||||
// for line in formatted.lines() {
|
||||
// write!(self.buf, "{}", line)?;
|
||||
// self.new_line()?;
|
||||
// }
|
||||
// }
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
use crate::{
|
||||
component::write_component, element::write_element, expr::write_raw_expr, util::write_tabs,
|
||||
};
|
||||
use dioxus_rsx::*;
|
||||
use std::fmt::{self, Write};
|
||||
|
||||
pub fn write_ident(
|
||||
buf: &mut String,
|
||||
lines: &[&str],
|
||||
node: &BodyNode,
|
||||
indent: usize,
|
||||
) -> fmt::Result {
|
||||
match node {
|
||||
BodyNode::Element(el) => write_element(el, buf, lines, indent),
|
||||
BodyNode::Component(component) => write_component(component, buf, indent, lines),
|
||||
BodyNode::Text(text) => write_text(text, buf, indent),
|
||||
BodyNode::RawExpr(exp) => write_raw_expr(exp, indent, lines, buf),
|
||||
}
|
||||
}
|
||||
|
||||
fn write_text(text: &syn::LitStr, buf: &mut String, indent: usize) -> fmt::Result {
|
||||
write!(buf, "\"{}\"", text.value())
|
||||
}
|
|
@ -1,8 +1,7 @@
|
|||
//! pretty printer for rsx code
|
||||
|
||||
use dioxus_rsx::*;
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::ToTokens;
|
||||
pub use crate::buffer::*;
|
||||
use crate::util::*;
|
||||
|
||||
mod block;
|
||||
mod buffer;
|
||||
|
@ -10,8 +9,75 @@ mod children;
|
|||
mod component;
|
||||
mod element;
|
||||
mod expr;
|
||||
mod ident;
|
||||
mod util;
|
||||
|
||||
pub use block::{fmt_block, get_format_blocks};
|
||||
pub use ident::write_ident;
|
||||
// pub use block::{fmt_block, get_format_blocks};
|
||||
|
||||
/// A modification to the original file to be applied by an IDE
|
||||
///
|
||||
/// Right now this re-writes entire rsx! blocks at a time, instead of precise line-by-line changes.
|
||||
///
|
||||
/// In a "perfect" world we would have tiny edits to preserve things like cursor states and selections. The API here makes
|
||||
/// it possible to migrate to a more precise modification approach in the future without breaking existing code.
|
||||
///
|
||||
/// Note that this is tailored to VSCode's TextEdit API and not a general Diff API. Line numbers are not accurate if
|
||||
/// multiple edits are applied in a single file without tracking text shifts.
|
||||
#[derive(serde::Deserialize, serde::Serialize, Clone, Debug, PartialEq, Hash)]
|
||||
pub struct FormattedBlock {
|
||||
/// The new contents of the block
|
||||
pub formatted: String,
|
||||
|
||||
/// The line number of the first line of the block.
|
||||
pub start: usize,
|
||||
|
||||
/// The end of the block, exclusive.
|
||||
pub end: usize,
|
||||
}
|
||||
|
||||
/// Format a file into a list of `FormattedBlock`s to be applied by an IDE for autoformatting.
|
||||
///
|
||||
/// This function expects a complete file, not just a block of code. To format individual rsx! blocks, use fmt_block instead.
|
||||
pub fn fmt_file(contents: &str) -> Vec<FormattedBlock> {
|
||||
let mut formatted_blocks = Vec::new();
|
||||
let mut last_bracket_end = 0;
|
||||
|
||||
use triple_accel::{levenshtein_search, Match};
|
||||
|
||||
for Match { end, k, start } in levenshtein_search(b"rsx! {", contents.as_bytes()) {
|
||||
// ensure the marker is not nested
|
||||
if start < last_bracket_end {
|
||||
continue;
|
||||
}
|
||||
|
||||
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 new = fmt_block(sub_string).unwrap();
|
||||
let stripped = &contents[end + 1..bracket_end + end - 1];
|
||||
|
||||
if stripped == new {
|
||||
continue;
|
||||
}
|
||||
|
||||
formatted_blocks.push(FormattedBlock {
|
||||
formatted: new,
|
||||
start: end,
|
||||
end: end + bracket_end - 1,
|
||||
});
|
||||
}
|
||||
|
||||
formatted_blocks
|
||||
}
|
||||
|
||||
pub fn fmt_block(block: &str) -> Option<String> {
|
||||
let mut buf = Buffer::new();
|
||||
let lines = block.split('\n').collect::<Vec<_>>();
|
||||
|
||||
for node in &syn::parse_str::<dioxus_rsx::CallBody>(block).ok()?.roots {
|
||||
buf.write_ident(&lines, node).ok()?;
|
||||
}
|
||||
|
||||
Some(buf.buf)
|
||||
}
|
||||
|
|
|
@ -14,13 +14,6 @@ pub fn extract_attr_len(attributes: &[ElementAttrNamed]) -> usize {
|
|||
.sum()
|
||||
}
|
||||
|
||||
pub fn write_tabs(f: &mut dyn Write, num: usize) -> std::fmt::Result {
|
||||
for _ in 0..num {
|
||||
write!(f, " ")?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn find_bracket_end(contents: &str) -> Option<usize> {
|
||||
let mut depth = 0;
|
||||
let mut i = 0;
|
||||
|
|
|
@ -2,6 +2,16 @@ use dioxus_autofmt::*;
|
|||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use syn::{Attribute, Meta};
|
||||
|
||||
fn test_block(wrong: &str, right: &str) {
|
||||
let formatted = fmt_block(wrong).unwrap();
|
||||
assert_eq!(formatted, right);
|
||||
}
|
||||
|
||||
fn print_block(wrong: &str) {
|
||||
let formatted = fmt_block(wrong).unwrap();
|
||||
println!("{}", formatted);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn formats_block() {
|
||||
let block = r#"
|
||||
|
@ -46,7 +56,7 @@ fn formats_block() {
|
|||
|
||||
let formatted = fmt_block(block).unwrap();
|
||||
|
||||
print!("{formatted}");
|
||||
println!("{formatted}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -62,6 +72,54 @@ fn parse_comment() {
|
|||
dbg!(parsed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn print_cases() {
|
||||
print_block(
|
||||
r#"
|
||||
div {
|
||||
adsasd: "asd",
|
||||
h1 {"asd"}
|
||||
div {
|
||||
div {
|
||||
"hello"
|
||||
}
|
||||
div {
|
||||
"goodbye"
|
||||
}
|
||||
div { class: "broccoli",
|
||||
div {
|
||||
"hi"
|
||||
}
|
||||
}
|
||||
div { class: "broccolibroccolibroccolibroccolibroccolibroccolibroccolibroccolibroccolibroccoli",
|
||||
div {
|
||||
"hi"
|
||||
}
|
||||
}
|
||||
div { class: "alksdjasd", onclick: move |_| {
|
||||
|
||||
liberty!();
|
||||
},
|
||||
div {
|
||||
"hi"
|
||||
}
|
||||
}
|
||||
|
||||
commented{
|
||||
// is unparalled
|
||||
class: "asdasd",
|
||||
|
||||
// My genius
|
||||
div {
|
||||
"hi"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn formats_component() {
|
||||
let block = r#"
|
||||
|
@ -76,7 +134,7 @@ fn formats_component() {
|
|||
|
||||
let formatted = fmt_block(block).unwrap();
|
||||
|
||||
print!("{formatted}");
|
||||
println!("{formatted}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -97,7 +155,7 @@ fn formats_element() {
|
|||
|
||||
let formatted = fmt_block(block).unwrap();
|
||||
|
||||
print!("{formatted}");
|
||||
println!("{formatted}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -118,7 +176,7 @@ fn formats_element_short() {
|
|||
|
||||
let formatted = fmt_block(block).unwrap();
|
||||
|
||||
print!("{formatted}");
|
||||
println!("{formatted}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -132,7 +190,25 @@ fn formats_element_nested() {
|
|||
|
||||
let formatted = fmt_block(block).unwrap();
|
||||
|
||||
print!("{formatted}");
|
||||
assert_eq!(
|
||||
formatted,
|
||||
r#"h3 { class: "mb-2 text-xl font-bold", "Invite Member" }"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn formats_element_props_on_top() {
|
||||
let block = r#"
|
||||
h3 {
|
||||
class: "mb-2 text-xl font-bold mb-2 text-xl font-bold mb-2 text-xl font-bold mb-2 text-xl font-bold mb-2 text-xl font-bold",
|
||||
|
||||
"Invite Member"
|
||||
}
|
||||
"#;
|
||||
|
||||
let formatted = fmt_block(block).unwrap();
|
||||
|
||||
println!("{formatted}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -143,7 +219,10 @@ fn formats_element_nested_no_trailing_tabs() {
|
|||
|
||||
let formatted = fmt_block(block).unwrap();
|
||||
|
||||
print!("{formatted}");
|
||||
assert_eq!(
|
||||
formatted,
|
||||
r#"img { class: "mb-6 mx-auto h-24", src: "artemis-assets/images/friends.png", alt: "" }"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -160,20 +239,20 @@ fn formats_element_with_correct_indent() {
|
|||
|
||||
let formatted = fmt_block(block).unwrap();
|
||||
|
||||
print!("{formatted}");
|
||||
println!("{formatted}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn small_elements_and_text_are_small() {
|
||||
let block = r###"
|
||||
a { class: " text-white",
|
||||
a { class: "text-white",
|
||||
"Send invitation"
|
||||
}
|
||||
"###;
|
||||
|
||||
let formatted = fmt_block(block).unwrap();
|
||||
|
||||
print!("{formatted}");
|
||||
assert_eq!(formatted, r#"a { class: "text-white", "Send invitation" }"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -192,7 +271,7 @@ fn formats_component_man_props() {
|
|||
|
||||
let formatted = fmt_block(block).unwrap();
|
||||
|
||||
print!("{formatted}");
|
||||
println!("{formatted}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -204,24 +283,24 @@ fn formats_component_tiny() {
|
|||
|
||||
let formatted = fmt_block(block).unwrap();
|
||||
|
||||
print!("{formatted}");
|
||||
println!("{formatted}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn formats_exprs() {
|
||||
let block = r#"
|
||||
ul {
|
||||
(0..10).map(|f| rsx!{
|
||||
li {
|
||||
"hi"
|
||||
}
|
||||
div {}
|
||||
(0..10).map(|f| rsx! {
|
||||
li { "hi" }
|
||||
})
|
||||
div {}
|
||||
}
|
||||
"#;
|
||||
|
||||
let formatted = fmt_block(block).unwrap();
|
||||
|
||||
print!("{formatted}");
|
||||
println!("{formatted}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -238,7 +317,7 @@ fn formats_exprs_neg_indent() {
|
|||
|
||||
let formatted = fmt_block(block).unwrap();
|
||||
|
||||
print!("{formatted}");
|
||||
println!("{formatted}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -261,7 +340,7 @@ fn formats_exprs_handlers() {
|
|||
|
||||
let formatted = fmt_block(block).unwrap();
|
||||
|
||||
print!("{formatted}");
|
||||
println!("{formatted}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -296,7 +375,7 @@ fn formats_complex() {
|
|||
|
||||
let formatted = fmt_block(block).unwrap();
|
||||
|
||||
print!("{formatted}");
|
||||
println!("{formatted}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -314,9 +393,9 @@ rsx!{
|
|||
|
||||
"#;
|
||||
|
||||
let formatted = get_format_blocks(block);
|
||||
let formatted = fmt_file(block);
|
||||
|
||||
print!("{formatted:?}");
|
||||
println!("{formatted:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -332,9 +411,9 @@ rsx!{
|
|||
}
|
||||
"#;
|
||||
|
||||
let formatted = get_format_blocks(block);
|
||||
let formatted = fmt_file(block);
|
||||
|
||||
print!("{formatted:?}");
|
||||
println!("{formatted:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -350,7 +429,7 @@ rsx! {
|
|||
}
|
||||
"#;
|
||||
|
||||
let formatted = get_format_blocks(src);
|
||||
let formatted = fmt_file(src);
|
||||
|
||||
println!("{formatted:?}");
|
||||
}
|
||||
|
@ -378,7 +457,7 @@ fn NavItem<'a>(cx: Scope, to: &'static str, children: Element<'a>, icon: Shape)
|
|||
"#
|
||||
.to_string();
|
||||
|
||||
let formatted = get_format_blocks(&src);
|
||||
let formatted = fmt_file(&src);
|
||||
|
||||
let block = formatted.into_iter().next().unwrap();
|
||||
|
||||
|
@ -446,7 +525,7 @@ fn NavItem<'a>(cx: Scope, to: &'static str, children: Element<'a>, icon: Shape)
|
|||
"#
|
||||
.to_string();
|
||||
|
||||
let formatted = get_format_blocks(&src);
|
||||
let formatted = fmt_file(&src);
|
||||
|
||||
dbg!(&formatted);
|
||||
|
||||
|
@ -469,7 +548,7 @@ pub fn Alert(cx: Scope) -> Element {
|
|||
"###
|
||||
.to_string();
|
||||
|
||||
let formatted = get_format_blocks(&src);
|
||||
let formatted = fmt_file(&src);
|
||||
|
||||
dbg!(&formatted);
|
||||
}
|
||||
|
|
|
@ -209,6 +209,16 @@ pub enum ElementAttr {
|
|||
/// onclick: {}
|
||||
EventTokens { name: Ident, tokens: Expr },
|
||||
}
|
||||
impl ElementAttr {
|
||||
pub fn is_expr(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
ElementAttr::AttrExpression { .. }
|
||||
| ElementAttr::CustomAttrExpression { .. }
|
||||
| ElementAttr::EventTokens { .. }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
pub struct ElementAttrNamed {
|
||||
|
|
Loading…
Reference in a new issue