lib.rs 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. use dioxus_rsx::CallBody;
  2. use crate::util::*;
  3. use crate::writer::*;
  4. mod buffer;
  5. mod component;
  6. mod element;
  7. mod expr;
  8. mod util;
  9. mod writer;
  10. /// A modification to the original file to be applied by an IDE
  11. ///
  12. /// Right now this re-writes entire rsx! blocks at a time, instead of precise line-by-line changes.
  13. ///
  14. /// In a "perfect" world we would have tiny edits to preserve things like cursor states and selections. The API here makes
  15. /// it possible to migrate to a more precise modification approach in the future without breaking existing code.
  16. ///
  17. /// Note that this is tailored to VSCode's TextEdit API and not a general Diff API. Line numbers are not accurate if
  18. /// multiple edits are applied in a single file without tracking text shifts.
  19. #[derive(serde::Deserialize, serde::Serialize, Clone, Debug, PartialEq, Eq, Hash)]
  20. pub struct FormattedBlock {
  21. /// The new contents of the block
  22. pub formatted: String,
  23. /// The line number of the first line of the block.
  24. pub start: usize,
  25. /// The end of the block, exclusive.
  26. pub end: usize,
  27. }
  28. /// Format a file into a list of `FormattedBlock`s to be applied by an IDE for autoformatting.
  29. ///
  30. /// This function expects a complete file, not just a block of code. To format individual rsx! blocks, use fmt_block instead.
  31. ///
  32. /// The point here is to provide precise modifications of a source file so an accompanying IDE tool can map these changes
  33. /// back to the file precisely.
  34. ///
  35. /// Nested blocks of RSX will be handled automatically
  36. pub fn fmt_file(contents: &str) -> Vec<FormattedBlock> {
  37. let mut formatted_blocks = Vec::new();
  38. let mut last_bracket_end = 0;
  39. use triple_accel::{levenshtein_search, Match};
  40. for Match { end, start, k } in levenshtein_search(b"rsx! {", contents.as_bytes()) {
  41. if k > 1 {
  42. continue;
  43. }
  44. // ensure the marker is not nested
  45. if start < last_bracket_end {
  46. continue;
  47. }
  48. let mut indent_level = {
  49. // walk backwards from start until we find a new line
  50. let mut lines = contents[..start].lines().rev();
  51. match lines.next() {
  52. Some(line) => {
  53. if line.starts_with("//") || line.starts_with("///") {
  54. continue;
  55. }
  56. line.chars().take_while(|c| *c == ' ').count() / 4
  57. }
  58. None => 0,
  59. }
  60. };
  61. let remaining = &contents[end - 1..];
  62. let bracket_end = find_bracket_end(remaining).unwrap();
  63. let sub_string = &contents[end..bracket_end + end - 1];
  64. last_bracket_end = bracket_end + end - 1;
  65. let mut new = fmt_block(sub_string, indent_level).unwrap();
  66. if new.len() <= 80 && !new.contains('\n') {
  67. new = format!(" {new} ");
  68. // if the new string is not multiline, don't try to adjust the marker ending
  69. // We want to trim off any indentation that there might be
  70. indent_level = 0;
  71. }
  72. let end_marker = end + bracket_end - indent_level * 4 - 1;
  73. if new == contents[end..end_marker] {
  74. continue;
  75. }
  76. formatted_blocks.push(FormattedBlock {
  77. formatted: new,
  78. start: end,
  79. end: end_marker,
  80. });
  81. }
  82. formatted_blocks
  83. }
  84. pub fn write_block_out(body: CallBody) -> Option<String> {
  85. let mut buf = Writer {
  86. src: vec!["".to_string()],
  87. ..Writer::default()
  88. };
  89. // Oneliner optimization
  90. if buf.is_short_children(&body.roots).is_some() {
  91. buf.write_ident(&body.roots[0]).unwrap();
  92. } else {
  93. buf.write_body_indented(&body.roots).unwrap();
  94. }
  95. buf.consume()
  96. }
  97. pub fn fmt_block(block: &str, indent_level: usize) -> Option<String> {
  98. let body = syn::parse_str::<dioxus_rsx::CallBody>(block).ok()?;
  99. let mut buf = Writer {
  100. src: block.lines().map(|f| f.to_string()).collect(),
  101. ..Writer::default()
  102. };
  103. buf.out.indent = indent_level;
  104. // Oneliner optimization
  105. if buf.is_short_children(&body.roots).is_some() {
  106. buf.write_ident(&body.roots[0]).unwrap();
  107. } else {
  108. buf.write_body_indented(&body.roots).unwrap();
  109. }
  110. // writing idents leaves the final line ended at the end of the last ident
  111. if buf.out.buf.contains('\n') {
  112. buf.out.new_line().unwrap();
  113. }
  114. buf.consume()
  115. }
  116. pub fn apply_format(input: &str, block: FormattedBlock) -> String {
  117. let start = block.start;
  118. let end = block.end;
  119. let (left, _) = input.split_at(start);
  120. let (_, right) = input.split_at(end);
  121. // dbg!(&block.formatted);
  122. format!("{}{}{}", left, block.formatted, right)
  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. }