hot_reloading_file_map.rs 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. use crate::{CallBody, HotReloadingContext};
  2. use dioxus_core::Template;
  3. use krates::cm::MetadataCommand;
  4. use krates::Cmd;
  5. pub use proc_macro2::TokenStream;
  6. pub use std::collections::HashMap;
  7. use std::path::PathBuf;
  8. pub use std::sync::Mutex;
  9. pub use std::time::SystemTime;
  10. pub use std::{fs, io, path::Path};
  11. pub use std::{fs::File, io::Read};
  12. pub use syn::__private::ToTokens;
  13. use syn::spanned::Spanned;
  14. use super::hot_reload_diff::{find_rsx, DiffResult};
  15. pub enum UpdateResult {
  16. UpdatedRsx(Vec<Template<'static>>),
  17. NeedsRebuild,
  18. }
  19. /// The result of building a FileMap
  20. pub struct FileMapBuildResult<Ctx: HotReloadingContext> {
  21. /// The FileMap that was built
  22. pub map: FileMap<Ctx>,
  23. /// Any errors that occurred while building the FileMap that were not fatal
  24. pub errors: Vec<io::Error>,
  25. }
  26. pub struct FileMap<Ctx: HotReloadingContext> {
  27. pub map: HashMap<PathBuf, (String, Option<Template<'static>>)>,
  28. in_workspace: HashMap<PathBuf, Option<PathBuf>>,
  29. phantom: std::marker::PhantomData<Ctx>,
  30. }
  31. impl<Ctx: HotReloadingContext> FileMap<Ctx> {
  32. /// Create a new FileMap from a crate directory
  33. pub fn create(path: PathBuf) -> io::Result<FileMapBuildResult<Ctx>> {
  34. Self::create_with_filter(path, |_| false)
  35. }
  36. /// Create a new FileMap from a crate directory
  37. pub fn create_with_filter(
  38. path: PathBuf,
  39. mut filter: impl FnMut(&Path) -> bool,
  40. ) -> io::Result<FileMapBuildResult<Ctx>> {
  41. struct FileMapSearchResult {
  42. map: HashMap<PathBuf, (String, Option<Template<'static>>)>,
  43. errors: Vec<io::Error>,
  44. }
  45. fn find_rs_files(
  46. root: PathBuf,
  47. filter: &mut impl FnMut(&Path) -> bool,
  48. ) -> io::Result<FileMapSearchResult> {
  49. let mut files = HashMap::new();
  50. let mut errors = Vec::new();
  51. if root.is_dir() {
  52. for entry in (fs::read_dir(root)?).flatten() {
  53. let path = entry.path();
  54. if !filter(&path) {
  55. let FileMapSearchResult {
  56. map,
  57. errors: child_errors,
  58. } = find_rs_files(path, filter)?;
  59. errors.extend(child_errors);
  60. files.extend(map);
  61. }
  62. }
  63. } else if root.extension().and_then(|s| s.to_str()) == Some("rs") {
  64. if let Ok(mut file) = File::open(root.clone()) {
  65. let mut src = String::new();
  66. file.read_to_string(&mut src)?;
  67. files.insert(root, (src, None));
  68. }
  69. }
  70. Ok(FileMapSearchResult { map: files, errors })
  71. }
  72. let FileMapSearchResult { map, errors } = find_rs_files(path, &mut filter)?;
  73. let result = Self {
  74. map,
  75. in_workspace: HashMap::new(),
  76. phantom: std::marker::PhantomData,
  77. };
  78. Ok(FileMapBuildResult {
  79. map: result,
  80. errors,
  81. })
  82. }
  83. /// Try to update the rsx in a file
  84. pub fn update_rsx(&mut self, file_path: &Path, crate_dir: &Path) -> io::Result<UpdateResult> {
  85. let mut file = File::open(file_path)?;
  86. let mut src = String::new();
  87. file.read_to_string(&mut src)?;
  88. if let Ok(syntax) = syn::parse_file(&src) {
  89. let in_workspace = self.child_in_workspace(crate_dir)?;
  90. if let Some((old_src, template_slot)) = self.map.get_mut(file_path) {
  91. if let Ok(old) = syn::parse_file(old_src) {
  92. match find_rsx(&syntax, &old) {
  93. DiffResult::CodeChanged => {
  94. self.map.insert(file_path.to_path_buf(), (src, None));
  95. }
  96. DiffResult::RsxChanged(changed) => {
  97. let mut messages: Vec<Template<'static>> = Vec::new();
  98. for (old, new) in changed.into_iter() {
  99. let old_start = old.span().start();
  100. if let (Ok(old_call_body), Ok(new_call_body)) = (
  101. syn::parse2::<CallBody>(old.tokens),
  102. syn::parse2::<CallBody>(new),
  103. ) {
  104. // if the file!() macro is invoked in a workspace, the path is relative to the workspace root, otherwise it's relative to the crate root
  105. // we need to check if the file is in a workspace or not and strip the prefix accordingly
  106. let prefix = if let Some(workspace) = &in_workspace {
  107. workspace
  108. } else {
  109. crate_dir
  110. };
  111. if let Ok(file) = file_path.strip_prefix(prefix) {
  112. let line = old_start.line;
  113. let column = old_start.column + 1;
  114. let location = file.display().to_string()
  115. + ":"
  116. + &line.to_string()
  117. + ":"
  118. + &column.to_string()
  119. // the byte index doesn't matter, but dioxus needs it
  120. + ":0";
  121. if let Some(template) = new_call_body
  122. .update_template::<Ctx>(
  123. Some(old_call_body),
  124. Box::leak(location.into_boxed_str()),
  125. )
  126. {
  127. // dioxus cannot handle empty templates
  128. if template.roots.is_empty() {
  129. return Ok(UpdateResult::NeedsRebuild);
  130. } else {
  131. // if the template is the same, don't send it
  132. if let Some(old_template) = template_slot {
  133. if old_template == &template {
  134. continue;
  135. }
  136. }
  137. *template_slot = Some(template);
  138. messages.push(template);
  139. }
  140. } else {
  141. return Ok(UpdateResult::NeedsRebuild);
  142. }
  143. }
  144. }
  145. }
  146. return Ok(UpdateResult::UpdatedRsx(messages));
  147. }
  148. }
  149. }
  150. } else {
  151. // if this is a new file, rebuild the project
  152. let FileMapBuildResult { map, mut errors } =
  153. FileMap::create(crate_dir.to_path_buf())?;
  154. if let Some(err) = errors.pop() {
  155. return Err(err);
  156. }
  157. *self = map;
  158. }
  159. }
  160. Ok(UpdateResult::NeedsRebuild)
  161. }
  162. fn child_in_workspace(&mut self, crate_dir: &Path) -> io::Result<Option<PathBuf>> {
  163. if let Some(in_workspace) = self.in_workspace.get(crate_dir) {
  164. Ok(in_workspace.clone())
  165. } else {
  166. let mut cmd = Cmd::new();
  167. let manafest_path = crate_dir.join("Cargo.toml");
  168. cmd.manifest_path(&manafest_path);
  169. let cmd: MetadataCommand = cmd.into();
  170. let metadata = cmd
  171. .exec()
  172. .map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
  173. let in_workspace = metadata.workspace_root != crate_dir;
  174. let workspace_path = in_workspace.then(|| metadata.workspace_root.into());
  175. self.in_workspace
  176. .insert(crate_dir.to_path_buf(), workspace_path.clone());
  177. Ok(workspace_path)
  178. }
  179. }
  180. }