1
0

lib.rs 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. use crate::writer::*;
  2. use collect_macros::byte_offset;
  3. use dioxus_rsx::{BodyNode, CallBody};
  4. use proc_macro2::LineColumn;
  5. use syn::{ExprMacro, MacroDelimiter};
  6. mod buffer;
  7. mod collect_macros;
  8. mod component;
  9. mod element;
  10. mod expr;
  11. mod writer;
  12. /// A modification to the original file to be applied by an IDE
  13. ///
  14. /// Right now this re-writes entire rsx! blocks at a time, instead of precise line-by-line changes.
  15. ///
  16. /// In a "perfect" world we would have tiny edits to preserve things like cursor states and selections. The API here makes
  17. /// it possible to migrate to a more precise modification approach in the future without breaking existing code.
  18. ///
  19. /// Note that this is tailored to VSCode's TextEdit API and not a general Diff API. Line numbers are not accurate if
  20. /// multiple edits are applied in a single file without tracking text shifts.
  21. #[derive(serde::Deserialize, serde::Serialize, Clone, Debug, PartialEq, Eq, Hash)]
  22. pub struct FormattedBlock {
  23. /// The new contents of the block
  24. pub formatted: String,
  25. /// The line number of the first line of the block.
  26. pub start: usize,
  27. /// The end of the block, exclusive.
  28. pub end: usize,
  29. }
  30. /// Format a file into a list of `FormattedBlock`s to be applied by an IDE for autoformatting.
  31. ///
  32. /// This function expects a complete file, not just a block of code. To format individual rsx! blocks, use fmt_block instead.
  33. ///
  34. /// The point here is to provide precise modifications of a source file so an accompanying IDE tool can map these changes
  35. /// back to the file precisely.
  36. ///
  37. /// Nested blocks of RSX will be handled automatically
  38. pub fn fmt_file(contents: &str) -> Vec<FormattedBlock> {
  39. let mut formatted_blocks = Vec::new();
  40. let parsed = syn::parse_file(contents).unwrap();
  41. let mut macros = vec![];
  42. collect_macros::collect_from_file(&parsed, &mut macros);
  43. // No macros, no work to do
  44. if macros.is_empty() {
  45. return formatted_blocks;
  46. }
  47. let mut writer = Writer {
  48. src: contents.lines().collect::<Vec<_>>(),
  49. ..Writer::default()
  50. };
  51. // Dont 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. // item.parse_body::<CallBody>();
  60. let body = item.parse_body::<CallBody>().unwrap();
  61. let rsx_start = macro_path.span().start();
  62. writer.out.indent = &writer.src[rsx_start.line - 1]
  63. .chars()
  64. .take_while(|c| *c == ' ')
  65. .count()
  66. / 4;
  67. // Oneliner optimization
  68. if writer.is_short_children(&body.roots).is_some() {
  69. writer.write_ident(&body.roots[0]).unwrap();
  70. } else {
  71. writer.write_body_indented(&body.roots).unwrap();
  72. }
  73. // writing idents leaves the final line ended at the end of the last ident
  74. if writer.out.buf.contains('\n') {
  75. writer.out.new_line().unwrap();
  76. writer.out.tab().unwrap();
  77. }
  78. let span = match item.delimiter {
  79. MacroDelimiter::Paren(b) => b.span,
  80. MacroDelimiter::Brace(b) => b.span,
  81. MacroDelimiter::Bracket(b) => b.span,
  82. };
  83. let mut formatted = String::new();
  84. std::mem::swap(&mut formatted, &mut writer.out.buf);
  85. let start = byte_offset(contents, span.start()) + 1;
  86. let end = byte_offset(contents, span.end()) - 1;
  87. // Rustfmt will remove the space between the macro and the opening paren if the macro is a single expression
  88. let body_is_solo_expr =
  89. body.roots.len() == 1 && matches!(body.roots[0], BodyNode::RawExpr(_));
  90. if formatted.len() <= 80 && !formatted.contains('\n') && !body_is_solo_expr {
  91. formatted = format!(" {} ", formatted);
  92. }
  93. end_span = span.end();
  94. if contents[start..end] == formatted {
  95. continue;
  96. }
  97. formatted_blocks.push(FormattedBlock {
  98. formatted,
  99. start,
  100. end,
  101. });
  102. }
  103. formatted_blocks
  104. }
  105. pub fn write_block_out(body: CallBody) -> Option<String> {
  106. let mut buf = Writer {
  107. src: vec![""],
  108. ..Writer::default()
  109. };
  110. // Oneliner optimization
  111. if buf.is_short_children(&body.roots).is_some() {
  112. buf.write_ident(&body.roots[0]).unwrap();
  113. } else {
  114. buf.write_body_indented(&body.roots).unwrap();
  115. }
  116. buf.consume()
  117. }
  118. pub fn fmt_block_from_expr(raw: &str, expr: ExprMacro) -> Option<String> {
  119. let body = syn::parse2::<CallBody>(expr.mac.tokens).unwrap();
  120. let mut buf = Writer {
  121. src: raw.lines().collect(),
  122. ..Writer::default()
  123. };
  124. // Oneliner optimization
  125. if buf.is_short_children(&body.roots).is_some() {
  126. buf.write_ident(&body.roots[0]).unwrap();
  127. } else {
  128. buf.write_body_indented(&body.roots).unwrap();
  129. }
  130. buf.consume()
  131. }
  132. pub fn fmt_block(block: &str, indent_level: usize) -> Option<String> {
  133. let body = syn::parse_str::<dioxus_rsx::CallBody>(block).unwrap();
  134. let mut buf = Writer {
  135. src: block.lines().collect(),
  136. ..Writer::default()
  137. };
  138. buf.out.indent = indent_level;
  139. // Oneliner optimization
  140. if buf.is_short_children(&body.roots).is_some() {
  141. buf.write_ident(&body.roots[0]).unwrap();
  142. } else {
  143. buf.write_body_indented(&body.roots).unwrap();
  144. }
  145. // writing idents leaves the final line ended at the end of the last ident
  146. if buf.out.buf.contains('\n') {
  147. buf.out.new_line().unwrap();
  148. }
  149. buf.consume()
  150. }
  151. pub fn apply_format(input: &str, block: FormattedBlock) -> String {
  152. let start = block.start;
  153. let end = block.end;
  154. let (left, _) = input.split_at(start);
  155. let (_, right) = input.split_at(end);
  156. // dbg!(&block.formatted);
  157. format!("{}{}{}", left, block.formatted, right)
  158. }
  159. // Apply all the blocks
  160. pub fn apply_formats(input: &str, blocks: Vec<FormattedBlock>) -> String {
  161. let mut out = String::new();
  162. let mut last = 0;
  163. for FormattedBlock {
  164. formatted,
  165. start,
  166. end,
  167. } in blocks
  168. {
  169. let prefix = &input[last..start];
  170. out.push_str(prefix);
  171. out.push_str(&formatted);
  172. last = end;
  173. }
  174. let suffix = &input[last..];
  175. out.push_str(suffix);
  176. out
  177. }