lib.rs 7.1 KB

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