lib.rs 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. #![doc = include_str!("../README.md")]
  2. #![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
  3. #![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
  4. use crate::writer::*;
  5. use dioxus_rsx::{BodyNode, CallBody};
  6. use proc_macro2::LineColumn;
  7. use syn::{parse::Parser, ExprMacro};
  8. mod buffer;
  9. mod collect_macros;
  10. mod indent;
  11. mod prettier_please;
  12. mod rsx_block;
  13. mod writer;
  14. pub use indent::{IndentOptions, IndentType};
  15. /// A modification to the original file to be applied by an IDE
  16. ///
  17. /// Right now this re-writes entire rsx! blocks at a time, instead of precise line-by-line changes.
  18. ///
  19. /// In a "perfect" world we would have tiny edits to preserve things like cursor states and selections. The API here makes
  20. /// it possible to migrate to a more precise modification approach in the future without breaking existing code.
  21. ///
  22. /// Note that this is tailored to VSCode's TextEdit API and not a general Diff API. Line numbers are not accurate if
  23. /// multiple edits are applied in a single file without tracking text shifts.
  24. #[derive(serde::Deserialize, serde::Serialize, Clone, Debug, PartialEq, Eq, Hash)]
  25. pub struct FormattedBlock {
  26. /// The new contents of the block
  27. pub formatted: String,
  28. /// The line number of the first line of the block.
  29. pub start: usize,
  30. /// The end of the block, exclusive.
  31. pub end: usize,
  32. }
  33. /// Format a file into a list of `FormattedBlock`s to be applied by an IDE for autoformatting.
  34. ///
  35. /// This function expects a complete file, not just a block of code. To format individual rsx! blocks, use fmt_block instead.
  36. ///
  37. /// The point here is to provide precise modifications of a source file so an accompanying IDE tool can map these changes
  38. /// back to the file precisely.
  39. ///
  40. /// Nested blocks of RSX will be handled automatically
  41. pub fn fmt_file(contents: &str, indent: IndentOptions) -> Vec<FormattedBlock> {
  42. let mut formatted_blocks = Vec::new();
  43. let parsed = syn::parse_file(contents).unwrap();
  44. let macros = collect_macros::collect_from_file(&parsed);
  45. // No macros, no work to do
  46. if macros.is_empty() {
  47. return formatted_blocks;
  48. }
  49. let mut writer = Writer::new(contents);
  50. writer.out.indent = indent;
  51. // Don't parse nested macros
  52. let mut end_span = LineColumn { column: 0, line: 0 };
  53. for item in macros {
  54. let macro_path = &item.path.segments[0].ident;
  55. // this macro is inside the last macro we parsed, skip it
  56. if macro_path.span().start() < end_span {
  57. continue;
  58. }
  59. let body = item.parse_body_with(CallBody::parse_strict).unwrap();
  60. let rsx_start = macro_path.span().start();
  61. writer.out.indent_level = writer
  62. .out
  63. .indent
  64. .count_indents(writer.src[rsx_start.line - 1]);
  65. // TESTME
  66. // If we fail to parse this macro then we have no choice to give up and return what we've got
  67. if writer.write_rsx_call(&body.body).is_err() {
  68. return formatted_blocks;
  69. }
  70. // writing idents leaves the final line ended at the end of the last ident
  71. if writer.out.buf.contains('\n') {
  72. writer.out.new_line().unwrap();
  73. writer.out.tab().unwrap();
  74. }
  75. let span = item.delimiter.span().join();
  76. let mut formatted = writer.out.buf.split_off(0);
  77. let start = collect_macros::byte_offset(contents, span.start()) + 1;
  78. let end = collect_macros::byte_offset(contents, span.end()) - 1;
  79. // Rustfmt will remove the space between the macro and the opening paren if the macro is a single expression
  80. let body_is_solo_expr = body.body.roots.len() == 1
  81. && matches!(body.body.roots[0], BodyNode::RawExpr(_) | BodyNode::Text(_));
  82. if formatted.len() <= 80 && !formatted.contains('\n') && !body_is_solo_expr {
  83. formatted = format!(" {formatted} ");
  84. }
  85. end_span = span.end();
  86. if contents[start..end] == formatted {
  87. continue;
  88. }
  89. formatted_blocks.push(FormattedBlock {
  90. formatted,
  91. start,
  92. end,
  93. });
  94. }
  95. formatted_blocks
  96. }
  97. /// Write a Callbody (the rsx block) to a string
  98. ///
  99. /// If the tokens can't be formatted, this returns None. This is usually due to an incomplete expression
  100. /// that passed partial expansion but failed to parse.
  101. pub fn write_block_out(body: &CallBody) -> Option<String> {
  102. let mut buf = Writer::new("");
  103. buf.write_rsx_call(&body.body).ok()?;
  104. buf.consume()
  105. }
  106. pub fn fmt_block_from_expr(raw: &str, expr: ExprMacro) -> Option<String> {
  107. let body = CallBody::parse_strict.parse2(expr.mac.tokens).unwrap();
  108. let mut buf = Writer::new(raw);
  109. buf.write_rsx_call(&body.body).ok()?;
  110. buf.consume()
  111. }
  112. pub fn fmt_block(block: &str, indent_level: usize, indent: IndentOptions) -> Option<String> {
  113. let body = CallBody::parse_strict.parse_str(block).unwrap();
  114. let mut buf = Writer::new(block);
  115. buf.out.indent = indent;
  116. buf.out.indent_level = indent_level;
  117. buf.write_rsx_call(&body.body).ok()?;
  118. // writing idents leaves the final line ended at the end of the last ident
  119. if buf.out.buf.contains('\n') {
  120. buf.out.new_line().unwrap();
  121. }
  122. buf.consume()
  123. }
  124. // Apply all the blocks
  125. pub fn apply_formats(input: &str, blocks: Vec<FormattedBlock>) -> String {
  126. let mut out = String::new();
  127. let mut last = 0;
  128. for FormattedBlock {
  129. formatted,
  130. start,
  131. end,
  132. } in blocks
  133. {
  134. let prefix = &input[last..start];
  135. out.push_str(prefix);
  136. out.push_str(&formatted);
  137. last = end;
  138. }
  139. let suffix = &input[last..];
  140. out.push_str(suffix);
  141. out
  142. }