mirror of
https://github.com/DioxusLabs/dioxus
synced 2025-02-17 06:08:26 +00:00
feat: implement comments in zero children nodes
This commit is contained in:
parent
6500a6d490
commit
1940855e39
9 changed files with 379 additions and 336 deletions
|
@ -1,33 +1,18 @@
|
|||
use std::{
|
||||
collections::{HashMap, VecDeque},
|
||||
fmt::{Result, Write},
|
||||
};
|
||||
//! The output buffer that supports some helpful methods
|
||||
//! These are separate from the input so we can lend references between the two
|
||||
//!
|
||||
//!
|
||||
//!
|
||||
|
||||
use dioxus_rsx::{BodyNode, ElementAttr, ElementAttrNamed, IfmtInput};
|
||||
use proc_macro2::{LineColumn, Span};
|
||||
use syn::{spanned::Spanned, Expr};
|
||||
use std::fmt::{Result, Write};
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
use dioxus_rsx::IfmtInput;
|
||||
|
||||
/// The output buffer that tracks indent and string
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Buffer {
|
||||
pub src: Vec<String>,
|
||||
pub cached_formats: HashMap<Location, String>,
|
||||
pub buf: String,
|
||||
pub indent: usize,
|
||||
pub comments: VecDeque<usize>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Hash, PartialEq, Eq, Debug)]
|
||||
pub struct Location {
|
||||
pub line: usize,
|
||||
pub col: usize,
|
||||
}
|
||||
impl Location {
|
||||
pub fn new(start: LineColumn) -> Self {
|
||||
Self {
|
||||
line: start.line,
|
||||
col: start.column,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Buffer {
|
||||
|
@ -62,162 +47,14 @@ impl Buffer {
|
|||
writeln!(self.buf)
|
||||
}
|
||||
|
||||
// Expects to be written directly into place
|
||||
pub fn write_ident(&mut self, node: &BodyNode) -> Result {
|
||||
match node {
|
||||
BodyNode::Element(el) => self.write_element(el),
|
||||
BodyNode::Component(component) => self.write_component(component),
|
||||
BodyNode::Text(text) => self.write_text(text),
|
||||
BodyNode::RawExpr(exp) => self.write_raw_expr(exp),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_text(&mut self, text: &IfmtInput) -> Result {
|
||||
write!(self.buf, "\"{}\"", text.source.as_ref().unwrap().value())
|
||||
}
|
||||
|
||||
pub fn consume(self) -> Option<String> {
|
||||
Some(self.buf)
|
||||
}
|
||||
|
||||
pub fn write_comments(&mut self, child: Span) -> Result {
|
||||
// collect all comments upwards
|
||||
let start = child.start();
|
||||
let line_start = start.line - 1;
|
||||
|
||||
for (id, line) in self.src[..line_start].iter().enumerate().rev() {
|
||||
if line.trim().starts_with("//") || line.is_empty() {
|
||||
if id != 0 {
|
||||
self.comments.push_front(id);
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let mut last_was_empty = false;
|
||||
while let Some(comment_line) = self.comments.pop_front() {
|
||||
let line = &self.src[comment_line];
|
||||
if line.is_empty() {
|
||||
if !last_was_empty {
|
||||
self.new_line()?;
|
||||
}
|
||||
last_was_empty = true;
|
||||
} else {
|
||||
last_was_empty = false;
|
||||
self.tabbed_line()?;
|
||||
write!(self.buf, "{}", self.src[comment_line].trim())?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Push out the indent level and write each component, line by line
|
||||
pub fn write_body_indented(&mut self, children: &[BodyNode]) -> Result {
|
||||
self.indent += 1;
|
||||
|
||||
self.write_body_no_indent(children)?;
|
||||
|
||||
self.indent -= 1;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_body_no_indent(&mut self, children: &[BodyNode]) -> Result {
|
||||
let last_child = children.len();
|
||||
let iter = children.iter().peekable().enumerate();
|
||||
|
||||
for (idx, child) in iter {
|
||||
if self.current_span_is_primary(child.span()) {
|
||||
self.write_comments(child.span())?;
|
||||
}
|
||||
|
||||
match child {
|
||||
// check if the expr is a short
|
||||
BodyNode::RawExpr { .. } => {
|
||||
self.tabbed_line()?;
|
||||
self.write_ident(child)?;
|
||||
if idx != last_child - 1 {
|
||||
write!(self.buf, ",")?;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
self.tabbed_line()?;
|
||||
self.write_ident(child)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn is_short_attrs(&mut self, attributes: &[ElementAttrNamed]) -> usize {
|
||||
let mut total = 0;
|
||||
|
||||
for attr in attributes {
|
||||
if self.current_span_is_primary(attr.attr.start()) {
|
||||
'line: for line in self.src[..attr.attr.start().start().line - 1].iter().rev() {
|
||||
match (line.trim().starts_with("//"), line.is_empty()) {
|
||||
(true, _) => return 100000,
|
||||
(_, true) => continue 'line,
|
||||
_ => break 'line,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
total += match &attr.attr {
|
||||
ElementAttr::AttrText { value, name } => {
|
||||
value.source.as_ref().unwrap().value().len() + name.span().line_length() + 3
|
||||
}
|
||||
ElementAttr::AttrExpression { name, value } => {
|
||||
value.span().line_length() + name.span().line_length() + 3
|
||||
}
|
||||
ElementAttr::CustomAttrText { value, name } => {
|
||||
value.source.as_ref().unwrap().value().len() + name.value().len() + 3
|
||||
}
|
||||
ElementAttr::CustomAttrExpression { name, value } => {
|
||||
name.value().len() + value.span().line_length() + 3
|
||||
}
|
||||
ElementAttr::EventTokens { tokens, name } => {
|
||||
let location = Location::new(tokens.span().start());
|
||||
|
||||
let len = if let std::collections::hash_map::Entry::Vacant(e) =
|
||||
self.cached_formats.entry(location)
|
||||
{
|
||||
let formatted = prettyplease::unparse_expr(tokens);
|
||||
let len = if formatted.contains('\n') {
|
||||
10000
|
||||
} else {
|
||||
formatted.len()
|
||||
};
|
||||
e.insert(formatted);
|
||||
len
|
||||
} else {
|
||||
self.cached_formats[&location].len()
|
||||
};
|
||||
|
||||
len + name.span().line_length() + 3
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
total
|
||||
}
|
||||
|
||||
pub fn retrieve_formatted_expr(&mut self, expr: &Expr) -> &str {
|
||||
self.cached_formats
|
||||
.entry(Location::new(expr.span().start()))
|
||||
.or_insert_with(|| prettyplease::unparse_expr(expr))
|
||||
.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
trait SpanLength {
|
||||
fn line_length(&self) -> usize;
|
||||
}
|
||||
impl SpanLength for Span {
|
||||
fn line_length(&self) -> usize {
|
||||
self.end().line - self.start().line
|
||||
impl std::fmt::Write for Buffer {
|
||||
fn write_str(&mut self, s: &str) -> std::fmt::Result {
|
||||
self.buf.push_str(s);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{buffer::Location, Buffer};
|
||||
use crate::{writer::Location, Writer};
|
||||
use dioxus_rsx::*;
|
||||
use quote::ToTokens;
|
||||
use std::fmt::{Result, Write};
|
||||
|
@ -19,7 +19,7 @@ enum ShortOptimization {
|
|||
NoOpt,
|
||||
}
|
||||
|
||||
impl Buffer {
|
||||
impl Writer {
|
||||
pub fn write_component(
|
||||
&mut self,
|
||||
Component {
|
||||
|
@ -28,6 +28,7 @@ impl Buffer {
|
|||
children,
|
||||
manual_props,
|
||||
prop_gen_args,
|
||||
..
|
||||
}: &Component,
|
||||
) -> Result {
|
||||
self.write_component_name(name, prop_gen_args)?;
|
||||
|
@ -82,46 +83,46 @@ impl Buffer {
|
|||
match opt_level {
|
||||
ShortOptimization::Empty => {}
|
||||
ShortOptimization::Oneliner => {
|
||||
write!(self.buf, " ")?;
|
||||
write!(self.out, " ")?;
|
||||
|
||||
self.write_component_fields(fields, manual_props, true)?;
|
||||
|
||||
if !children.is_empty() && !fields.is_empty() {
|
||||
write!(self.buf, ", ")?;
|
||||
write!(self.out, ", ")?;
|
||||
}
|
||||
|
||||
for child in children {
|
||||
self.write_ident(child)?;
|
||||
}
|
||||
|
||||
write!(self.buf, " ")?;
|
||||
write!(self.out, " ")?;
|
||||
}
|
||||
|
||||
ShortOptimization::PropsOnTop => {
|
||||
write!(self.buf, " ")?;
|
||||
write!(self.out, " ")?;
|
||||
self.write_component_fields(fields, manual_props, true)?;
|
||||
|
||||
if !children.is_empty() && !fields.is_empty() {
|
||||
write!(self.buf, ",")?;
|
||||
write!(self.out, ",")?;
|
||||
}
|
||||
|
||||
self.write_body_indented(children)?;
|
||||
self.tabbed_line()?;
|
||||
self.out.tabbed_line()?;
|
||||
}
|
||||
|
||||
ShortOptimization::NoOpt => {
|
||||
self.write_component_fields(fields, manual_props, false)?;
|
||||
|
||||
if !children.is_empty() && !fields.is_empty() {
|
||||
write!(self.buf, ",")?;
|
||||
write!(self.out, ",")?;
|
||||
}
|
||||
|
||||
self.write_body_indented(children)?;
|
||||
self.tabbed_line()?;
|
||||
self.out.tabbed_line()?;
|
||||
}
|
||||
}
|
||||
|
||||
write!(self.buf, "}}")?;
|
||||
write!(self.out, "}}")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -133,16 +134,16 @@ impl Buffer {
|
|||
let mut name = name.to_token_stream().to_string();
|
||||
name.retain(|c| !c.is_whitespace());
|
||||
|
||||
write!(self.buf, "{name}")?;
|
||||
write!(self.out, "{name}")?;
|
||||
|
||||
if let Some(generics) = generics {
|
||||
let mut written = generics.to_token_stream().to_string();
|
||||
written.retain(|c| !c.is_whitespace());
|
||||
|
||||
write!(self.buf, "{}", written)?;
|
||||
write!(self.out, "{}", written)?;
|
||||
}
|
||||
|
||||
write!(self.buf, " {{")?;
|
||||
write!(self.out, " {{")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -157,18 +158,18 @@ impl Buffer {
|
|||
|
||||
while let Some(field) = field_iter.next() {
|
||||
if !sameline {
|
||||
self.indented_tabbed_line()?;
|
||||
self.out.indented_tabbed_line()?;
|
||||
}
|
||||
|
||||
let name = &field.name;
|
||||
match &field.content {
|
||||
ContentField::ManExpr(exp) => {
|
||||
let out = prettyplease::unparse_expr(exp);
|
||||
write!(self.buf, "{}: {}", name, out)?;
|
||||
write!(self.out, "{}: {}", name, out)?;
|
||||
}
|
||||
ContentField::Formatted(s) => {
|
||||
write!(
|
||||
self.buf,
|
||||
self.out,
|
||||
"{}: \"{}\"",
|
||||
name,
|
||||
s.source.as_ref().unwrap().value()
|
||||
|
@ -178,27 +179,27 @@ impl Buffer {
|
|||
let out = prettyplease::unparse_expr(exp);
|
||||
let mut lines = out.split('\n').peekable();
|
||||
let first = lines.next().unwrap();
|
||||
write!(self.buf, "{}: {}", name, first)?;
|
||||
write!(self.out, "{}: {}", name, first)?;
|
||||
for line in lines {
|
||||
self.new_line()?;
|
||||
self.indented_tab()?;
|
||||
write!(self.buf, "{}", line)?;
|
||||
self.out.new_line()?;
|
||||
self.out.indented_tab()?;
|
||||
write!(self.out, "{}", line)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if field_iter.peek().is_some() || manual_props.is_some() {
|
||||
write!(self.buf, ",")?;
|
||||
write!(self.out, ",")?;
|
||||
|
||||
if sameline {
|
||||
write!(self.buf, " ")?;
|
||||
write!(self.out, " ")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(exp) = manual_props {
|
||||
if !sameline {
|
||||
self.indented_tabbed_line()?;
|
||||
self.out.indented_tabbed_line()?;
|
||||
}
|
||||
self.write_manual_props(exp)?;
|
||||
}
|
||||
|
@ -258,10 +259,10 @@ impl Buffer {
|
|||
|
||||
let first_line = lines.next().unwrap();
|
||||
|
||||
write!(self.buf, "..{first_line}")?;
|
||||
write!(self.out, "..{first_line}")?;
|
||||
for line in lines {
|
||||
self.indented_tabbed_line()?;
|
||||
write!(self.buf, "{line}")?;
|
||||
self.out.indented_tabbed_line()?;
|
||||
write!(self.out, "{line}")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -1,35 +1,56 @@
|
|||
use crate::Buffer;
|
||||
use crate::Writer;
|
||||
use dioxus_rsx::*;
|
||||
use proc_macro2::Span;
|
||||
use std::{fmt::Result, fmt::Write};
|
||||
use syn::{spanned::Spanned, Expr};
|
||||
use std::{
|
||||
fmt::Result,
|
||||
fmt::{self, Write},
|
||||
};
|
||||
use syn::{spanned::Spanned, token::Brace, Expr};
|
||||
|
||||
#[derive(Debug)]
|
||||
enum ShortOptimization {
|
||||
// Special because we want to print the closing bracket immediately
|
||||
/// Special because we want to print the closing bracket immediately
|
||||
///
|
||||
/// IE
|
||||
/// `div {}` instead of `div { }`
|
||||
Empty,
|
||||
|
||||
// Special optimization to put everything on the same line
|
||||
/// Special optimization to put everything on the same line and add some buffer spaces
|
||||
///
|
||||
/// IE
|
||||
///
|
||||
/// `div { "asdasd" }` instead of a multiline variant
|
||||
Oneliner,
|
||||
|
||||
// Optimization where children flow but props remain fixed on top
|
||||
/// Optimization where children flow but props remain fixed on top
|
||||
PropsOnTop,
|
||||
|
||||
// The noisiest optimization where everything flows
|
||||
/// The noisiest optimization where everything flows
|
||||
NoOpt,
|
||||
}
|
||||
|
||||
impl Buffer {
|
||||
pub fn write_element(
|
||||
&mut self,
|
||||
Element {
|
||||
/*
|
||||
// whitespace
|
||||
div {
|
||||
// some whitespace
|
||||
class: "asdasd"
|
||||
|
||||
// whjiot
|
||||
asdasd // whitespace
|
||||
}
|
||||
*/
|
||||
|
||||
impl Writer {
|
||||
pub fn write_element(&mut self, el: &Element) -> Result {
|
||||
let Element {
|
||||
name,
|
||||
key,
|
||||
attributes,
|
||||
children,
|
||||
_is_static,
|
||||
}: &Element,
|
||||
) -> Result {
|
||||
brace,
|
||||
} = el;
|
||||
|
||||
/*
|
||||
1. Write the tag
|
||||
2. Write the key
|
||||
|
@ -37,7 +58,7 @@ impl Buffer {
|
|||
4. Write the children
|
||||
*/
|
||||
|
||||
write!(self.buf, "{name} {{")?;
|
||||
write!(self.out, "{name} {{")?;
|
||||
|
||||
// decide if we have any special optimizations
|
||||
// Default with none, opt the cases in one-by-one
|
||||
|
@ -70,6 +91,9 @@ impl Buffer {
|
|||
// If there's nothing at all, empty optimization
|
||||
if attributes.is_empty() && children.is_empty() && key.is_none() {
|
||||
opt_level = ShortOptimization::Empty;
|
||||
|
||||
// Write comments if they exist
|
||||
self.write_todo_body(brace)?;
|
||||
}
|
||||
|
||||
// multiline handlers bump everything down
|
||||
|
@ -80,56 +104,56 @@ impl Buffer {
|
|||
match opt_level {
|
||||
ShortOptimization::Empty => {}
|
||||
ShortOptimization::Oneliner => {
|
||||
write!(self.buf, " ")?;
|
||||
write!(self.out, " ")?;
|
||||
|
||||
self.write_attributes(attributes, key, true)?;
|
||||
|
||||
if !children.is_empty() && (!attributes.is_empty() || key.is_some()) {
|
||||
write!(self.buf, ", ")?;
|
||||
write!(self.out, ", ")?;
|
||||
}
|
||||
|
||||
for (id, child) in children.iter().enumerate() {
|
||||
self.write_ident(child)?;
|
||||
if id != children.len() - 1 && children.len() > 1 {
|
||||
write!(self.buf, ", ")?;
|
||||
write!(self.out, ", ")?;
|
||||
}
|
||||
}
|
||||
|
||||
write!(self.buf, " ")?;
|
||||
write!(self.out, " ")?;
|
||||
}
|
||||
|
||||
ShortOptimization::PropsOnTop => {
|
||||
if !attributes.is_empty() || key.is_some() {
|
||||
write!(self.buf, " ")?;
|
||||
write!(self.out, " ")?;
|
||||
}
|
||||
self.write_attributes(attributes, key, true)?;
|
||||
|
||||
if !children.is_empty() && (!attributes.is_empty() || key.is_some()) {
|
||||
write!(self.buf, ",")?;
|
||||
write!(self.out, ",")?;
|
||||
}
|
||||
|
||||
if !children.is_empty() {
|
||||
self.write_body_indented(children)?;
|
||||
}
|
||||
self.tabbed_line()?;
|
||||
self.out.tabbed_line()?;
|
||||
}
|
||||
|
||||
ShortOptimization::NoOpt => {
|
||||
self.write_attributes(attributes, key, false)?;
|
||||
|
||||
if !children.is_empty() && (!attributes.is_empty() || key.is_some()) {
|
||||
write!(self.buf, ",")?;
|
||||
write!(self.out, ",")?;
|
||||
}
|
||||
|
||||
if !children.is_empty() {
|
||||
self.write_body_indented(children)?;
|
||||
}
|
||||
|
||||
self.tabbed_line()?;
|
||||
self.out.tabbed_line()?;
|
||||
}
|
||||
}
|
||||
|
||||
write!(self.buf, "}}")?;
|
||||
write!(self.out, "}}")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -144,39 +168,39 @@ impl Buffer {
|
|||
|
||||
if let Some(key) = key {
|
||||
if !sameline {
|
||||
self.indented_tabbed_line()?;
|
||||
self.out.indented_tabbed_line()?;
|
||||
}
|
||||
write!(
|
||||
self.buf,
|
||||
self.out,
|
||||
"key: \"{}\"",
|
||||
key.source.as_ref().unwrap().value()
|
||||
)?;
|
||||
if !attributes.is_empty() {
|
||||
write!(self.buf, ",")?;
|
||||
write!(self.out, ",")?;
|
||||
if sameline {
|
||||
write!(self.buf, " ")?;
|
||||
write!(self.out, " ")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while let Some(attr) = attr_iter.next() {
|
||||
self.indent += 1;
|
||||
self.out.indent += 1;
|
||||
if !sameline {
|
||||
self.write_comments(attr.attr.start())?;
|
||||
}
|
||||
self.indent -= 1;
|
||||
self.out.indent -= 1;
|
||||
|
||||
if !sameline {
|
||||
self.indented_tabbed_line()?;
|
||||
self.out.indented_tabbed_line()?;
|
||||
}
|
||||
|
||||
self.write_attribute(attr)?;
|
||||
|
||||
if attr_iter.peek().is_some() {
|
||||
write!(self.buf, ",")?;
|
||||
write!(self.out, ",")?;
|
||||
|
||||
if sameline {
|
||||
write!(self.buf, " ")?;
|
||||
write!(self.out, " ")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -188,19 +212,19 @@ impl Buffer {
|
|||
match &attr.attr {
|
||||
ElementAttr::AttrText { name, value } => {
|
||||
write!(
|
||||
self.buf,
|
||||
self.out,
|
||||
"{name}: \"{value}\"",
|
||||
value = value.source.as_ref().unwrap().value()
|
||||
)?;
|
||||
}
|
||||
ElementAttr::AttrExpression { name, value } => {
|
||||
let out = prettyplease::unparse_expr(value);
|
||||
write!(self.buf, "{}: {}", name, out)?;
|
||||
write!(self.out, "{}: {}", name, out)?;
|
||||
}
|
||||
|
||||
ElementAttr::CustomAttrText { name, value } => {
|
||||
write!(
|
||||
self.buf,
|
||||
self.out,
|
||||
"\"{name}\": \"{value}\"",
|
||||
name = name.value(),
|
||||
value = value.source.as_ref().unwrap().value()
|
||||
|
@ -209,7 +233,7 @@ impl Buffer {
|
|||
|
||||
ElementAttr::CustomAttrExpression { name, value } => {
|
||||
let out = prettyplease::unparse_expr(value);
|
||||
write!(self.buf, "\"{}\": {}", name.value(), out)?;
|
||||
write!(self.out, "\"{}\": {}", name.value(), out)?;
|
||||
}
|
||||
|
||||
ElementAttr::EventTokens { name, tokens } => {
|
||||
|
@ -221,17 +245,17 @@ impl Buffer {
|
|||
// a one-liner for whatever reason
|
||||
// Does not need a new line
|
||||
if lines.peek().is_none() {
|
||||
write!(self.buf, "{}: {}", name, first)?;
|
||||
write!(self.out, "{}: {}", name, first)?;
|
||||
} else {
|
||||
writeln!(self.buf, "{}: {}", name, first)?;
|
||||
writeln!(self.out, "{}: {}", name, first)?;
|
||||
|
||||
while let Some(line) = lines.next() {
|
||||
self.indented_tab()?;
|
||||
write!(self.buf, "{}", line)?;
|
||||
self.out.indented_tab()?;
|
||||
write!(self.out, "{}", line)?;
|
||||
if lines.peek().is_none() {
|
||||
write!(self.buf, "")?;
|
||||
write!(self.out, "")?;
|
||||
} else {
|
||||
writeln!(self.buf)?;
|
||||
writeln!(self.out)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -255,8 +279,6 @@ impl Buffer {
|
|||
""
|
||||
};
|
||||
|
||||
// dbg!(beginning);
|
||||
|
||||
beginning.is_empty()
|
||||
}
|
||||
|
||||
|
@ -269,6 +291,10 @@ impl Buffer {
|
|||
if children.is_empty() {
|
||||
// todo: allow elements with comments but no children
|
||||
// like div { /* comment */ }
|
||||
// or
|
||||
// div {
|
||||
// // some helpful
|
||||
// }
|
||||
return Some(0);
|
||||
}
|
||||
|
||||
|
@ -343,6 +369,35 @@ impl Buffer {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// empty everything except for some comments
|
||||
fn write_todo_body(&mut self, brace: &Brace) -> fmt::Result {
|
||||
let span = brace.span.span();
|
||||
let start = span.start();
|
||||
let end = span.end();
|
||||
|
||||
if start.line == end.line {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
writeln!(self.out)?;
|
||||
|
||||
for idx in start.line..end.line {
|
||||
let line = &self.src[idx];
|
||||
if line.trim().starts_with("//") {
|
||||
for _ in 0..self.out.indent + 1 {
|
||||
write!(self.out, " ")?
|
||||
}
|
||||
writeln!(self.out, "{}", line.trim()).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
for _ in 0..self.out.indent {
|
||||
write!(self.out, " ")?
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn get_expr_length(expr: &Expr) -> Option<usize> {
|
||||
|
|
|
@ -1,45 +1,23 @@
|
|||
//! pretty printer for rsx!
|
||||
use std::fmt::{Result, Write};
|
||||
|
||||
use crate::Buffer;
|
||||
use crate::Writer;
|
||||
|
||||
impl Buffer {
|
||||
impl Writer {
|
||||
pub fn write_raw_expr(&mut self, exp: &syn::Expr) -> Result {
|
||||
/*
|
||||
We want to normalize the expr to the appropriate indent level.
|
||||
*/
|
||||
|
||||
// in a perfect world, just fire up the rust pretty printer
|
||||
// pretty_print_rust_code_as_if_it_were_rustfmt()
|
||||
|
||||
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;
|
||||
|
||||
// print comments
|
||||
// let mut queued_comments = vec![];
|
||||
// let mut offset = 2;
|
||||
// loop {
|
||||
// let line = &self.src[start.line - offset];
|
||||
// if line.trim_start().starts_with("//") {
|
||||
// queued_comments.push(line);
|
||||
// } else {
|
||||
// break;
|
||||
// }
|
||||
|
||||
// offset += 1;
|
||||
// }
|
||||
// let had_comments = !queued_comments.is_empty();
|
||||
// for comment in queued_comments.into_iter().rev() {
|
||||
// writeln!(self.buf, "{}", comment)?;
|
||||
// }
|
||||
|
||||
// if the expr is on one line, just write it directly
|
||||
if start.line == end.line {
|
||||
write!(
|
||||
self.buf,
|
||||
self.out,
|
||||
"{}",
|
||||
&self.src[start.line - 1][start.column - 1..end.column].trim()
|
||||
)?;
|
||||
|
@ -50,7 +28,7 @@ impl Buffer {
|
|||
// This involves unshifting the first line if it's aligned
|
||||
let first_line = &self.src[start.line - 1];
|
||||
write!(
|
||||
self.buf,
|
||||
self.out,
|
||||
"{}",
|
||||
&first_line[start.column - 1..first_line.len()].trim()
|
||||
)?;
|
||||
|
@ -66,7 +44,7 @@ impl Buffer {
|
|||
};
|
||||
|
||||
for (id, line) in self.src[start.line..end.line].iter().enumerate() {
|
||||
writeln!(self.buf)?;
|
||||
writeln!(self.out)?;
|
||||
// trim the leading whitespace
|
||||
let line = match id {
|
||||
x if x == (end.line - start.line) - 1 => &line[..end.column],
|
||||
|
@ -75,52 +53,17 @@ impl Buffer {
|
|||
|
||||
if offset < 0 {
|
||||
for _ in 0..-offset {
|
||||
write!(self.buf, " ")?;
|
||||
write!(self.out, " ")?;
|
||||
}
|
||||
|
||||
write!(self.buf, "{}", line)?;
|
||||
write!(self.out, "{}", line)?;
|
||||
} else {
|
||||
let offset = offset as usize;
|
||||
let right = &line[offset..];
|
||||
write!(self.buf, "{}", right)?;
|
||||
write!(self.out, "{}", right)?;
|
||||
}
|
||||
}
|
||||
|
||||
// let first = &self.src[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 (row, line) in self.src[start.line - 1..end.line].iter().enumerate() {
|
||||
// let line = match row {
|
||||
// 0 => &line[start.column - 1..],
|
||||
// a if a == (end.line - start.line) => &line[..end.column - 1],
|
||||
// _ => 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)?;
|
||||
// }
|
||||
// }
|
||||
|
||||
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,13 +1,14 @@
|
|||
use dioxus_rsx::CallBody;
|
||||
|
||||
use crate::buffer::*;
|
||||
use crate::util::*;
|
||||
use crate::writer::*;
|
||||
|
||||
mod buffer;
|
||||
mod component;
|
||||
mod element;
|
||||
mod expr;
|
||||
mod util;
|
||||
mod writer;
|
||||
|
||||
/// A modification to the original file to be applied by an IDE
|
||||
///
|
||||
|
@ -101,10 +102,9 @@ pub fn fmt_file(contents: &str) -> Vec<FormattedBlock> {
|
|||
}
|
||||
|
||||
pub fn write_block_out(body: CallBody) -> Option<String> {
|
||||
let mut buf = Buffer {
|
||||
let mut buf = Writer {
|
||||
src: vec!["".to_string()],
|
||||
indent: 0,
|
||||
..Buffer::default()
|
||||
..Writer::default()
|
||||
};
|
||||
|
||||
// Oneliner optimization
|
||||
|
@ -120,12 +120,13 @@ 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 mut buf = Buffer {
|
||||
let mut buf = Writer {
|
||||
src: block.lines().map(|f| f.to_string()).collect(),
|
||||
indent: indent_level,
|
||||
..Buffer::default()
|
||||
..Writer::default()
|
||||
};
|
||||
|
||||
buf.out.indent = indent_level;
|
||||
|
||||
// Oneliner optimization
|
||||
if buf.is_short_children(&body.roots).is_some() {
|
||||
buf.write_ident(&body.roots[0]).unwrap();
|
||||
|
@ -134,8 +135,8 @@ pub fn fmt_block(block: &str, indent_level: usize) -> Option<String> {
|
|||
}
|
||||
|
||||
// writing idents leaves the final line ended at the end of the last ident
|
||||
if buf.buf.contains('\n') {
|
||||
buf.new_line().unwrap();
|
||||
if buf.out.buf.contains('\n') {
|
||||
buf.out.new_line().unwrap();
|
||||
}
|
||||
|
||||
buf.consume()
|
||||
|
|
188
packages/autofmt/src/writer.rs
Normal file
188
packages/autofmt/src/writer.rs
Normal file
|
@ -0,0 +1,188 @@
|
|||
use dioxus_rsx::{BodyNode, ElementAttr, ElementAttrNamed};
|
||||
use proc_macro2::{LineColumn, Span};
|
||||
use std::{
|
||||
collections::{HashMap, VecDeque},
|
||||
fmt::{Result, Write},
|
||||
};
|
||||
use syn::{spanned::Spanned, Expr};
|
||||
|
||||
use crate::buffer::Buffer;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Writer {
|
||||
pub src: Vec<String>,
|
||||
pub cached_formats: HashMap<Location, String>,
|
||||
pub comments: VecDeque<usize>,
|
||||
pub out: Buffer,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Hash, PartialEq, Eq, Debug)]
|
||||
pub struct Location {
|
||||
pub line: usize,
|
||||
pub col: usize,
|
||||
}
|
||||
impl Location {
|
||||
pub fn new(start: LineColumn) -> Self {
|
||||
Self {
|
||||
line: start.line,
|
||||
col: start.column,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Writer {
|
||||
// Expects to be written directly into place
|
||||
pub fn write_ident(&mut self, node: &BodyNode) -> Result {
|
||||
match node {
|
||||
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(()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn consume(self) -> Option<String> {
|
||||
Some(self.out.buf)
|
||||
}
|
||||
|
||||
pub fn write_comments(&mut self, child: Span) -> Result {
|
||||
// collect all comments upwards
|
||||
let start = child.start();
|
||||
let line_start = start.line - 1;
|
||||
|
||||
for (id, line) in self.src[..line_start].iter().enumerate().rev() {
|
||||
if line.trim().starts_with("//") || line.is_empty() {
|
||||
if id != 0 {
|
||||
self.comments.push_front(id);
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let mut last_was_empty = false;
|
||||
while let Some(comment_line) = self.comments.pop_front() {
|
||||
let line = &self.src[comment_line];
|
||||
if line.is_empty() {
|
||||
if !last_was_empty {
|
||||
self.out.new_line()?;
|
||||
}
|
||||
last_was_empty = true;
|
||||
} else {
|
||||
last_was_empty = false;
|
||||
self.out.tabbed_line()?;
|
||||
write!(self.out, "{}", self.src[comment_line].trim())?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Push out the indent level and write each component, line by line
|
||||
pub fn write_body_indented(&mut self, children: &[BodyNode]) -> Result {
|
||||
self.out.indent += 1;
|
||||
|
||||
self.write_body_no_indent(children)?;
|
||||
|
||||
self.out.indent -= 1;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_body_no_indent(&mut self, children: &[BodyNode]) -> Result {
|
||||
let last_child = children.len();
|
||||
let iter = children.iter().peekable().enumerate();
|
||||
|
||||
for (idx, child) in iter {
|
||||
if self.current_span_is_primary(child.span()) {
|
||||
self.write_comments(child.span())?;
|
||||
}
|
||||
|
||||
match child {
|
||||
// check if the expr is a short
|
||||
BodyNode::RawExpr { .. } => {
|
||||
self.out.tabbed_line()?;
|
||||
self.write_ident(child)?;
|
||||
if idx != last_child - 1 {
|
||||
write!(self.out, ",")?;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
self.out.tabbed_line()?;
|
||||
self.write_ident(child)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn is_short_attrs(&mut self, attributes: &[ElementAttrNamed]) -> usize {
|
||||
let mut total = 0;
|
||||
|
||||
for attr in attributes {
|
||||
if self.current_span_is_primary(attr.attr.start()) {
|
||||
'line: for line in self.src[..attr.attr.start().start().line - 1].iter().rev() {
|
||||
match (line.trim().starts_with("//"), line.is_empty()) {
|
||||
(true, _) => return 100000,
|
||||
(_, true) => continue 'line,
|
||||
_ => break 'line,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
total += match &attr.attr {
|
||||
ElementAttr::AttrText { value, name } => {
|
||||
value.source.as_ref().unwrap().value().len() + name.span().line_length() + 3
|
||||
}
|
||||
ElementAttr::AttrExpression { name, value } => {
|
||||
value.span().line_length() + name.span().line_length() + 3
|
||||
}
|
||||
ElementAttr::CustomAttrText { value, name } => {
|
||||
value.source.as_ref().unwrap().value().len() + name.value().len() + 3
|
||||
}
|
||||
ElementAttr::CustomAttrExpression { name, value } => {
|
||||
name.value().len() + value.span().line_length() + 3
|
||||
}
|
||||
ElementAttr::EventTokens { tokens, name } => {
|
||||
let location = Location::new(tokens.span().start());
|
||||
|
||||
let len = if let std::collections::hash_map::Entry::Vacant(e) =
|
||||
self.cached_formats.entry(location)
|
||||
{
|
||||
let formatted = prettyplease::unparse_expr(tokens);
|
||||
let len = if formatted.contains('\n') {
|
||||
10000
|
||||
} else {
|
||||
formatted.len()
|
||||
};
|
||||
e.insert(formatted);
|
||||
len
|
||||
} else {
|
||||
self.cached_formats[&location].len()
|
||||
};
|
||||
|
||||
len + name.span().line_length() + 3
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
total
|
||||
}
|
||||
|
||||
pub fn retrieve_formatted_expr(&mut self, expr: &Expr) -> &str {
|
||||
self.cached_formats
|
||||
.entry(Location::new(expr.span().start()))
|
||||
.or_insert_with(|| prettyplease::unparse_expr(expr))
|
||||
.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
trait SpanLength {
|
||||
fn line_length(&self) -> usize;
|
||||
}
|
||||
impl SpanLength for Span {
|
||||
fn line_length(&self) -> usize {
|
||||
self.end().line - self.start().line
|
||||
}
|
||||
}
|
|
@ -5,7 +5,7 @@ rsx! {
|
|||
class: "asdasd",
|
||||
|
||||
// Comments
|
||||
"hello world",
|
||||
"hello world"
|
||||
|
||||
// Comments
|
||||
expr1,
|
||||
|
@ -18,6 +18,25 @@ rsx! {
|
|||
// Comments
|
||||
// Comments
|
||||
// Comments
|
||||
expr3
|
||||
expr3,
|
||||
|
||||
div {
|
||||
// todo some work in here
|
||||
}
|
||||
|
||||
div {
|
||||
// todo some work in here
|
||||
// todo some work in here
|
||||
//
|
||||
// todo some work in here
|
||||
}
|
||||
|
||||
div {
|
||||
// todo some work in here
|
||||
class: "hello world",
|
||||
|
||||
// todo some work in here
|
||||
class: "hello world"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,8 +19,7 @@ use syn::{
|
|||
ext::IdentExt,
|
||||
parse::{Parse, ParseBuffer, ParseStream},
|
||||
spanned::Spanned,
|
||||
token, AngleBracketedGenericArguments, Error, Expr, Ident, LitStr, PathArguments, Result,
|
||||
Token,
|
||||
AngleBracketedGenericArguments, Error, Expr, Ident, LitStr, PathArguments, Result, Token,
|
||||
};
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
|
||||
|
@ -30,6 +29,7 @@ pub struct Component {
|
|||
pub fields: Vec<ComponentField>,
|
||||
pub children: Vec<BodyNode>,
|
||||
pub manual_props: Option<Expr>,
|
||||
pub brace: syn::token::Brace,
|
||||
}
|
||||
|
||||
impl Component {
|
||||
|
@ -88,13 +88,9 @@ impl Parse for Component {
|
|||
|
||||
// if we see a `{` then we have a block
|
||||
// else parse as a function-like call
|
||||
if stream.peek(token::Brace) {
|
||||
syn::braced!(content in stream);
|
||||
} else {
|
||||
syn::parenthesized!(content in stream);
|
||||
}
|
||||
let brace = syn::braced!(content in stream);
|
||||
|
||||
let mut body = Vec::new();
|
||||
let mut fields = Vec::new();
|
||||
let mut children = Vec::new();
|
||||
let mut manual_props = None;
|
||||
|
||||
|
@ -104,7 +100,7 @@ impl Parse for Component {
|
|||
content.parse::<Token![..]>()?;
|
||||
manual_props = Some(content.parse::<Expr>()?);
|
||||
} else if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
|
||||
body.push(content.parse::<ComponentField>()?);
|
||||
fields.push(content.parse::<ComponentField>()?);
|
||||
} else {
|
||||
children.push(content.parse::<BodyNode>()?);
|
||||
}
|
||||
|
@ -117,9 +113,10 @@ impl Parse for Component {
|
|||
Ok(Self {
|
||||
name,
|
||||
prop_gen_args,
|
||||
fields: body,
|
||||
fields,
|
||||
children,
|
||||
manual_props,
|
||||
brace,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ pub struct Element {
|
|||
pub attributes: Vec<ElementAttrNamed>,
|
||||
pub children: Vec<BodyNode>,
|
||||
pub _is_static: bool,
|
||||
pub brace: syn::token::Brace,
|
||||
}
|
||||
|
||||
impl Parse for Element {
|
||||
|
@ -25,7 +26,7 @@ impl Parse for Element {
|
|||
|
||||
// parse the guts
|
||||
let content: ParseBuffer;
|
||||
syn::braced!(content in stream);
|
||||
let brace = syn::braced!(content in stream);
|
||||
|
||||
let mut attributes: Vec<ElementAttrNamed> = vec![];
|
||||
let mut children: Vec<BodyNode> = vec![];
|
||||
|
@ -152,6 +153,7 @@ impl Parse for Element {
|
|||
name: el_name,
|
||||
attributes,
|
||||
children,
|
||||
brace,
|
||||
_is_static: false,
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue