use ra_ap_base_db::SourceDatabase; use ra_ap_hir::{EditionedFileId, HirDisplay}; use ra_ap_ide::{FilePosition, FileRange, MonikerResult}; use ra_ap_ide_db::defs::Definition; use ra_ap_ide_db::RootDatabase; use std::collections::{HashMap, HashSet}; use std::path::PathBuf; use std::sync::{mpsc, Arc, Mutex}; use pathdiff::diff_paths; use ra_ap_ide::{ Analysis, AnalysisHost, HlTag, LineCol, NavigationTarget, SymbolKind, TryToNav, UpmappingResult, }; use ra_ap_load_cargo::{load_workspace_at, LoadCargoConfig, ProcMacroServerChoice}; use ra_ap_project_model::CargoConfig; use ra_ap_syntax::{AstNode, TextRange}; use ra_ap_vfs::{AbsPathBuf, FileId, Vfs}; use rayon::prelude::*; use crate::db_writer_thread::Message; use crate::tag::Tag; use crate::utils::print_err; use crate::{html_generator, manifest_generator, ProjectInfo}; pub struct Generator { source_dir: AbsPathBuf, analysis_host: Arc>, vfs: Vfs, sender: mpsc::Sender, crate_name_by_versioned_name: HashMap, // e.g pathdiff-1.0.0 => pathdiff } /// Given path to crate root file /// Returns the directory that contains Cargo.toml fn get_crate_manifest(file_path: &ra_ap_vfs::VfsPath) -> Option { use ra_ap_project_model::ProjectManifest; match ProjectManifest::discover_single(file_path.as_path()?) { Ok(p) => match p { ProjectManifest::ProjectJson(p) => Some(p.to_path_buf()), ProjectManifest::CargoToml(p) => Some(p.to_path_buf()), ProjectManifest::CargoScript(p) => Some(p.to_path_buf()), }, Err(e) => { print_err(&format!( "Failed to find crate root for: {:?}, skipping! Error:\n{}", file_path, e )); None } } } /// Given path to crate root file /// Returns the directory that contains Cargo.toml fn get_crate_path(crate_root_file_path: &ra_ap_vfs::VfsPath) -> Option { Some( get_crate_manifest(crate_root_file_path)? .parent()? .to_path_buf(), ) } fn get_relative_filepath( vfs: &Vfs, rootpath: &AbsPathBuf, file_id: ra_ap_ide::FileId, ) -> Option { Some( vfs.file_path(file_id) .as_path()? .strip_prefix(rootpath)? .as_str() .to_string(), ) } impl Generator { pub fn new( package_dir: String, sender: mpsc::Sender, index_std: bool, ) -> Result> { let mut cargo_config = CargoConfig::default(); if index_std { cargo_config.sysroot = Some(ra_ap_project_model::RustLibSource::Discover); } let load_cargo_config = LoadCargoConfig { load_out_dirs_from_check: true, with_proc_macro_server: ProcMacroServerChoice::None, prefill_caches: false, }; let (db, vfs, _proc_macro) = { load_workspace_at( &PathBuf::from(&package_dir), &cargo_config, &load_cargo_config, &|_| { // println!("running {}",c); }, )? }; let host = AnalysisHost::with_database(db); let mut unique_crate_names = HashSet::new(); let mut unique_crates = HashMap::new(); // fetch all crate dependencies if let Ok(crate_deps) = host.analysis().fetch_crates() { println!("Fetched {} crates", crate_deps.len()); for dep in crate_deps { if let (Some(name), Some(version)) = (dep.name, dep.version) { if unique_crate_names.contains(&name) { // print_err(&format!("Skipped: {name}")); continue; } unique_crate_names.insert(name.clone()); unique_crates.insert(format!("{name}-{version}"), name.clone()); // store an underscore replaced variant as well. Sometimes the crates // underscores are replaced to - in the directory name, sometimes not let name2 = name.replace("_", "-"); unique_crates.insert(format!("{name2}-{version}"), name.clone()); } } } let host_lock = Mutex::new(host); let source_dir = std::env::current_dir()?.join(&package_dir); Ok(Self { source_dir: AbsPathBuf::assert_utf8(source_dir), analysis_host: host_lock.into(), vfs, sender, crate_name_by_versioned_name: unique_crates, }) } // #[allow(dead_code)] // fn dump_sym(&self, source: &Arc, sym: String, file_range: FileRange) { // let line = self.get_line_number(file_range); // let s: usize = file_range.range.start().into(); // let e: usize = file_range.range.end().into(); // println!("--------------- {sym} ==> {} line:{line}", &source[s..e]); // } fn get_line_number(&self, analysis: &Analysis, file_range: FileRange) -> u32 { let line_col = analysis .file_line_index(file_range.file_id) .unwrap() .try_line_col(file_range.range.start()); if line_col.is_none() { print_err(&format!( "Failed to get line number for file_range {:?}", file_range )); } line_col.unwrap_or(LineCol { line: 0, col: 0 }).line + 1 } /// returns the relative path for given [file_id] fn get_relative_path(&self, file_id: FileId) -> Result { // path to crate src/lib.rs let file_path = self.vfs.file_path(file_id); // path to crate registry let path = get_crate_path(file_path).ok_or("Failed to get crate path..")?; let path = path .parent() .ok_or("Failed to get parent of crate path")? .to_path_buf(); return get_relative_filepath(&self.vfs, &path, file_id) .map(|p| { // Try to convert cratename-1.0.0 => cratename if let Some((crate_name_with_version, _)) = p.as_str().split_once("/") { if let Some(name) = self .crate_name_by_versioned_name .get(crate_name_with_version) { let mut p = p.clone(); p.replace_range(..crate_name_with_version.len(), name); return p; } } p }) .ok_or("Failed to calculate relative path..".into()); } /// Get href link to symbol outside this module fn get_href_to_symbol( &self, analysis: &Analysis, def_file_id: FileId, base: &String, def_symbol_line_num: u32, relative_crates_path: &str, ) -> Option { // If the file is outside this crate let is_lib_file = analysis.is_library_file(def_file_id).ok()?; if is_lib_file { // get relative path for this file which maybe in a different crate let rel_path = self.get_relative_path(def_file_id); if let Ok(rel_path) = rel_path { return Some(format!( "href=\"{relative_crates_path}/{rel_path}.html#{def_symbol_line_num}\"" )); } else { let err = rel_path.unwrap_err(); print_err(&format!( "-> Failed to get relative path for file: {} --- error: {err}", self.vfs.file_path(def_file_id) )); } } // get definition file let def_file_path = self.vfs.file_path(def_file_id).to_string(); // Calculate relative path let relative_path = diff_paths(def_file_path, base)?; Some(format!( "href=\"{}.html#{def_symbol_line_num}\"", relative_path.as_path().display() )) } fn crate_for_file(analysis: &Analysis, file_id: FileId) -> Option { return analysis.crates_for(file_id).ok()?.pop().map(Into::into); } fn is_def_private(def: &Definition, db: &ra_ap_ide_db::RootDatabase) -> bool { def.visibility(db) .zip(def.module(db)) .and_then(|(visibility, def_module)| { if let ra_ap_hir::Visibility::Module(m, _) = visibility { return Some(ra_ap_hir::Module::from(m) == def_module); } None }) .unwrap_or_default() } fn build_attributes( &self, analysis: &Analysis, db: &RootDatabase, css_class: &str, defs_map: &HashMap, range: TextRange, current_file_id: &FileId, current_file_dir: &String, current_file_path: &str, refs_path: &AbsPathBuf, crates_path: &PathBuf, ) -> Option<(bool, String)> { let def = self.get_nav_target(analysis, *current_file_id, range, defs_map)?; let is_trait = css_class == "trait"; let is_def = def.file_id == *current_file_id && def.focus_or_full_range() == range; let file_range = FileRange { file_id: def.file_id, range: def.focus_or_full_range(), }; let mut href = String::new(); let line = self.get_line_number(analysis, file_range); if !is_def { // This is a reference if *current_file_id != file_range.file_id { if let Some(href_to_sym) = self.get_href_to_symbol( &analysis, file_range.file_id, current_file_dir, line, crates_path.to_str().unwrap(), ) { href = href_to_sym; } else { print_err(&format!( "Failed to get symbol href {}:{}", self.vfs.file_path(*current_file_id), self.get_line_number(analysis, file_range), )); } } else { // reference inside this file href = format!("href=\"#{line}\""); } } let pos = FilePosition { file_id: *current_file_id, offset: range.start(), }; let definition = defs_map.get(&range).map(|(def, _)| def); // create a unique identifier for data-ref let fd = def.file_id.index(); let st: usize = def.full_range.start().into(); let data_ref = format!("{}.{}.{}", fd, st, def.name); let is_private = definition.map_or(false, |def| Self::is_def_private(&def, db)); let local = is_private.then_some(" local").unwrap_or(""); let mut title = is_private .then_some(def.name.to_string()) .unwrap_or_default(); // get moniker let moniker = { if is_private { // no need to get moniker for private symbols, they are local to the file None } // Try to read moniker from definition else if let (Some(def), Some(krate)) = (definition, Self::crate_for_file(analysis, *current_file_id)) { let moniker_result = MonikerResult::from_def(db, *def, krate); moniker_result.and_then(|r| match r { MonikerResult::Moniker(moniker) => Some(moniker), MonikerResult::Local { enclosing_moniker } => enclosing_moniker, }) } // fallback to using analysis to get moniker else if let Ok(Some(m)) = analysis.moniker(pos) { let moniker_result = m.info.first().cloned(); moniker_result.and_then(|r| match r { MonikerResult::Moniker(moniker) => Some(moniker), MonikerResult::Local { enclosing_moniker } => enclosing_moniker, }) } else { None } }; if let Some(mon) = moniker { title = mon.identifier.to_string(); let sym_ref_filename = data_ref.replace("::", ".."); let sym_res_path = refs_path.join(&sym_ref_filename).to_string(); if is_def { if let Some(doc) = &def.docs { let doc = doc.as_str(); let msg = Message::WriteToFile( sym_res_path.clone(), format!("{doc}"), ); let _ = self.sender.send(msg); } Self::write_to_fn_search_index( &def.name, &title, &sym_ref_filename, &refs_path.parent().unwrap().join("fnSearch"), &self.sender, ); let tag = if is_trait { "dec" } else { "def" }; let msg = Message::WriteToFile( sym_res_path, format!("<{tag} f='{current_file_path}' l='{line}'/>"), ); let _ = self.sender.send(msg); } else { let line = self.get_line_number( analysis, FileRange { file_id: *current_file_id, range, }, ); let tag = if is_trait { "imp" } else { "use" }; let msg = Message::WriteToFile( sym_res_path, format!("<{tag} f='{current_file_path}' l='{line}'/>"), ); let _ = self.sender.send(msg); } } let css_class = if is_trait { "type trait" } else { css_class }; if is_def { Some(( true, format!("class=\"{css_class} decl def{local}\" title=\"{title}\" data-ref=\"{data_ref}\""), )) } else { Some(( false, format!( "{href} class=\"{css_class} ref{local}\" title=\"{title}\" data-ref=\"{data_ref}\"" ), )) } } fn get_file_defs( &self, db: &RootDatabase, file_id: EditionedFileId, ) -> HashMap { use ra_ap_ide_db::defs::{IdentClass, NameClass, NameRefClass}; let sema = ra_ap_hir::Semantics::new(db); let source = sema.parse(file_id).syntax().clone(); let def_map: HashMap = source .descendants() .filter_map(|n| { let text_range = n.text_range(); let name_like = ra_ap_syntax::ast::NameLike::cast(n)?; let classified = IdentClass::classify_node(&sema, &name_like.syntax())?; let def = match classified { IdentClass::NameClass( NameClass::ConstReference(it) | NameClass::Definition(it), ) => Some(it), IdentClass::NameClass(NameClass::PatFieldShorthand { local_def, .. }) => { Some(Definition::Local(local_def)) } IdentClass::NameRefClass(NameRefClass::Definition(it, _)) => Some(it), IdentClass::NameRefClass(NameRefClass::FieldShorthand { local_ref, .. }) => { let def = Definition::Local(local_ref); Some(def) } // We dont handle ExternCrateDecl _ => None, }; def.and_then(|def| { // only accept if we have a valid navigation file_range def.try_to_nav(db) .map(UpmappingResult::call_site) .map(|nav| (text_range, (def, nav))) }) }) .collect(); def_map } fn get_nav_target( &self, analysis: &Analysis, file_id: FileId, range: TextRange, defs_map: &HashMap, ) -> Option { let def = defs_map.get(&range); let res = def.map_or_else( || { // fallback to analysis analysis .goto_definition(FilePosition { file_id, offset: range.start(), }) .ok() .flatten() .and_then(|res| { if !res.info.is_empty() { return res.info.first().cloned(); } None }) }, |(_, nav_target)| { // we have a definition for this range in defs_map Some(nav_target.clone()) }, ); res } fn generate_tags( &self, source: &str, editioned_file_id: EditionedFileId, file_id: FileId, this_file_dir: &String, relative_file_path: &str, refs_path: &AbsPathBuf, relative_crates_path: &PathBuf, ) -> Vec { let hc = ra_ap_ide::HighlightConfig { strings: true, punctuation: true, specialize_punctuation: true, operator: true, specialize_operator: true, inject_doc_comment: true, macro_bang: true, syntactic_name_ref_highlighting: true, }; let (analysis, db) = match self.analysis_host.lock() { Ok(host) => (host.analysis(), host.raw_database().clone()), Err(_) => { print_err("generate_tags: Failed to lock and get analysis_host"); return Vec::new(); } }; let defs_map = self.get_file_defs(&db, editioned_file_id); let hints = analysis .inlay_hints( &ra_ap_ide::InlayHintsConfig { render_colons: true, discriminant_hints: ra_ap_ide::DiscriminantHints::Never, type_hints: true, parameter_hints: true, chaining_hints: true, closure_return_type_hints: ra_ap_ide::ClosureReturnTypeHints::WithBlock, lifetime_elision_hints: ra_ap_ide::LifetimeElisionHints::Never, adjustment_hints: ra_ap_ide::AdjustmentHints::Never, adjustment_hints_mode: ra_ap_ide::AdjustmentHintsMode::Prefix, adjustment_hints_hide_outside_unsafe: false, implicit_drop_hints: false, hide_named_constructor_hints: false, hide_closure_initialization_hints: false, closure_style: ra_ap_hir::ClosureStyle::ImplFn, param_names_for_lifetime_elision_hints: false, binding_mode_hints: false, max_length: Some(25), closure_capture_hints: false, closing_brace_hints_min_lines: Some(25), fields_to_resolve: ra_ap_ide::InlayFieldsToResolve::empty(), range_exclusive_hints: false, sized_bound: false, generic_parameter_hints: ra_ap_ide::GenericParameterHints { type_hints: false, lifetime_hints: false, const_hints: false, }, hide_closure_parameter_hints: false, }, file_id, None, ) .unwrap_or_default(); let mut last_hint = 0; let mut tags: Vec = Vec::new(); let hl = analysis.highlight(hc, file_id).unwrap(); for r in hl { let s = u32::from(r.range.start()); let e = u32::from(r.range.end()); let (name, attributes) = match r.highlight.tag { HlTag::Symbol(symbol) => match symbol { SymbolKind::Attribute => ("q", String::new()), SymbolKind::BuiltinAttr => ("q", String::new()), SymbolKind::Const => ("", String::new()), SymbolKind::ConstParam => ("", String::new()), SymbolKind::Derive => ("", String::new()), SymbolKind::DeriveHelper => ("", String::new()), SymbolKind::Field | SymbolKind::Function | SymbolKind::Method | SymbolKind::Macro | SymbolKind::Struct | SymbolKind::Variant | SymbolKind::Enum | SymbolKind::Union | SymbolKind::TypeAlias => { let css_class = match symbol { SymbolKind::Field => "field", SymbolKind::Method | SymbolKind::Function => "fn", SymbolKind::Macro => "macro", SymbolKind::Struct | SymbolKind::Enum | SymbolKind::Union => "type", SymbolKind::Variant => "enum", SymbolKind::TypeAlias => "typedef", _ => panic!("unexpected symbol"), }; let (is_def, mut attrs) = self .build_attributes( &analysis, &db, css_class, &defs_map, r.range, &file_id, this_file_dir, relative_file_path, refs_path, relative_crates_path, ) .unwrap_or_default(); if attrs.is_empty() { attrs += "class=\""; attrs += css_class; attrs += "\""; } let name = if is_def { "dfn" } else { "a" }; (name, attrs) } SymbolKind::Impl => ("", String::new()), SymbolKind::Label => ("", String::new()), SymbolKind::LifetimeParam => ("", String::new()), SymbolKind::Local | SymbolKind::ValueParam => { let title = &source[s as usize..e as usize]; let res = self.get_nav_target(&analysis, file_id, r.range, &defs_map); let (id, href, is_def, data_ref, data_type) = res .map(|def| { let is_def = def.focus_or_full_range() == r.range; let file_range = FileRange { file_id, range: def.focus_or_full_range(), }; let mut href = String::new(); if !is_def { let line = self.get_line_number(&analysis, file_range); href = format!("href=\"#{line}\" ") } let def_start_loc: u32 = def.focus_or_full_range().start().into(); let data_ref = format!("data-ref=\"{def_start_loc}\""); let id = format!("id=\"{def_start_loc}\""); let hover_config = ra_ap_ide::HoverConfig { links_in_hover: false, memory_layout: None, documentation: false, keywords: true, format: ra_ap_ide::HoverDocFormat::PlainText, max_trait_assoc_items_count: None, max_fields_count: None, max_enum_variants_count: None, max_subst_ty_len: ra_ap_ide::SubstTyLen::LimitTo(12), show_drop_glue: false, }; // compute data type from hover markup let mut data_type = String::new(); if let Some((Definition::Local(local), _)) = defs_map.get(&r.range) { let db: &RootDatabase = &db; let crates = analysis.crates_for(file_id).unwrap_or_default(); let dt = crates.first().map(|c| { ra_ap_hir::DisplayTarget::from_crate(db, c.clone()) }); if let Some(dt) = dt { let local_ty = local.ty(db); let ty = local_ty.display(db, dt); data_type = format!("data-type=\"{}\"", ty); } } else if let Ok(Some(hover)) = analysis.hover(&hover_config, file_range) { data_type = format!("data-type=\"{}\"", hover.info.markup); } (id, href, is_def, data_ref, data_type) }) .unwrap_or_default(); let name = if is_def { "dfn" } else { "a" }; let attrs: String = if is_def { format!("{id} class=\"local decl\" {data_ref} {data_type} title=\"{title}\"") } else { format!("{href}class=\"local ref\" {data_ref} title=\"{title}\"") }; (name, attrs) } SymbolKind::Module => ("span", String::from("class=\"namespace\"")), SymbolKind::SelfParam => ("", String::new()), SymbolKind::SelfType => ("", String::new()), SymbolKind::Static => ("b", String::new()), SymbolKind::ToolModule => ("", String::new()), SymbolKind::Trait => { let (is_def, mut attrs) = self .build_attributes( &analysis, &db, "trait", &defs_map, r.range, &file_id, this_file_dir, relative_file_path, refs_path, relative_crates_path, ) .unwrap_or_default(); if attrs.is_empty() { attrs += "class=\"type\""; } let name = if is_def { "dfn" } else { "a" }; (name, attrs) } SymbolKind::TraitAlias => ("", String::new()), SymbolKind::TypeParam => ("", String::new()), SymbolKind::InlineAsmRegOrRegClass => ("", String::new()), SymbolKind::ProcMacro => ("", String::new()), }, HlTag::AttributeBracket => ("", String::new()), HlTag::BuiltinType => ("b", String::new()), HlTag::ByteLiteral | HlTag::CharLiteral | HlTag::StringLiteral => { ("q", String::new()) } HlTag::Comment => ("i", String::new()), HlTag::EscapeSequence => ("var", String::new()), HlTag::FormatSpecifier => ("", String::new()), HlTag::Keyword => ("b", String::new()), HlTag::NumericLiteral | HlTag::BoolLiteral => ("var", String::new()), HlTag::Operator(_) => ("", String::new()), HlTag::Punctuation(_) => ("", String::new()), HlTag::UnresolvedReference => ("", String::new()), HlTag::InvalidEscapeSequence | HlTag::None => ("", String::new()), }; if name.is_empty() && attributes.is_empty() { continue; } use crate::tag::InlayHint; use crate::tag::Position; let mut hint: Option = None; for (idx, h) in hints.iter().skip(last_hint).enumerate() { let hint_start = u32::from(h.range.start()); if hint_start == s { last_hint = last_hint + idx + 1; hint = Some(InlayHint { pos: match h.position { ra_ap_ide::InlayHintPosition::Before => Position::Before, ra_ap_ide::InlayHintPosition::After => Position::After, }, label: h.label.to_string(), }); break; } else if hint_start > s { break; } } tags.push(Tag { start: s, end: e, name: name.to_string(), attributes: if attributes.is_empty() { None } else { Some(attributes) }, inlay_hint: hint, }); } tags.sort_by(|a, b| a.start.cmp(&b.start)); tags } fn all_modules_and_project_manifests(&self) -> (Vec, Vec) { let timer = std::time::Instant::now(); println!("Collecting modules"); let analysis_host = self.analysis_host.lock().unwrap(); let db = analysis_host.raw_database(); let mut worklist: Vec<_> = ra_ap_hir::Crate::all(db) .into_iter() .map(|krate| krate.root_module()) .collect(); let mut modules = Vec::new(); let mut unique_manifests = HashSet::new(); for root in worklist.iter() { let file_id = root.definition_source_file_id(db).original_file(db); let Some(manifest) = get_crate_manifest(self.vfs.file_path(file_id.file_id(db))) else { continue; }; unique_manifests.insert(manifest); } let manifests: Vec<_> = unique_manifests.into_iter().collect(); while let Some(module) = worklist.pop() { modules.push(module); worklist.extend(module.children(db)); } let mut unique = HashSet::new(); let work = modules .into_iter() .filter_map(|module| { let file_id = module.definition_source_file_id(db).original_file(db); // let source_root = db.file_source_root(file_id); // let source_root = db.source_root(source_root); // if source_root.is_library { // return None; // } // !source_root.is_library if unique.insert(file_id) { return Some(file_id); } None }) .collect(); println!("Collected modules in {:.2?}", timer.elapsed()); (work, manifests) } pub fn generate( &mut self, output_path: &AbsPathBuf, data_path: &AbsPathBuf, crates_path: &AbsPathBuf, project_info: &ProjectInfo, ) { let (all_modules, manifests) = self.all_modules_and_project_manifests(); let root_dir = output_path; let file_index = root_dir.join("fileIndex").to_string(); let refs_path = root_dir.join("refs"); std::fs::create_dir_all(&refs_path).expect("Failed to create refs dir..."); let fn_search_path = root_dir.join("fnSearch"); std::fs::create_dir_all(&fn_search_path).expect("Failed to create fnSearch dir..."); let total_files = all_modules.len(); let current_file_num = Mutex::new(1); manifest_generator::genereate_project_manfiests( &self.source_dir, manifests, data_path, output_path, project_info, &self.crate_name_by_versioned_name, ); all_modules.par_iter().for_each(|editioned_file_id| { let Ok(file_id) = self .analysis_host .lock() .map(|host| editioned_file_id.file_id(host.raw_database())) else { return; }; let (relative_file_path, is_lib_file, source, crate_info) = match self.analysis_host.lock() { Ok(host) => { let is_lib_file = host .analysis() .is_library_file(file_id) .ok() .unwrap_or(false); let (rel_path, crate_info) = if is_lib_file { let crate_info = Self::crate_for_file(&host.analysis(), file_id) .and_then(|c| { c.display_name(host.raw_database()) .zip(c.version(host.raw_database())) .map(|(n, v)| ProjectInfo { name: n.to_string(), version: v, }) }); (self.get_relative_path(file_id).ok(), crate_info) } else { ( get_relative_filepath(&self.vfs, &self.source_dir, file_id), None, ) }; let file_text = host.raw_database().file_text(file_id); ( rel_path, is_lib_file, file_text.text(host.raw_database()), crate_info, ) } Err(_) => (None, false, "".into(), None), }; // join out_dir + file so that we get out/src/main.rs.html let Some(relative_file_path) = relative_file_path else { print_err(&format!( "ERROR: Failed to get relative file path for {:?}, output_path: {:?}", self.vfs.file_path(file_id), output_path )); return; }; let output_path = if is_lib_file { root_dir.join("crates") } else { root_dir.join(&project_info.name) }; let output_file_path = output_path.join(&relative_file_path); let output_file_dir = output_file_path.parent().unwrap(); // get relative path from output root directory let Some(relative_file_path) = output_file_path .strip_prefix(root_dir) .map(|p| p.as_str().to_string()) else { print_err(&format!( "Failed to get relative file path for {relative_file_path}" )); return; }; // Write this file to fileIndex let _ = self.sender.send(Message::WriteToFile( file_index.clone(), relative_file_path.to_string(), )); // ensure all parent dirs exist if let Err(res) = std::fs::create_dir_all(output_file_dir) { print_err(&format!( "Failed to create dirs: {output_file_dir}, error: {res}", )); return; } // data path relative to this file let Some(relative_data_path) = diff_paths(data_path, output_file_dir) else { print_err(&format!( "Failed to create data path for file {relative_file_path}" )); return; }; // find root dir i.e., output dir relative to this file let relative_root_dir = diff_paths(root_dir, output_file_dir).unwrap(); // Absolute path to this file's dir as String let this_file_dir = match self.vfs.file_path(file_id).parent() { Some(dir) => dir.to_string(), None => { print_err(&format!("Failed to get dir for file {relative_file_path}")); return; } }; // relative path to crates dir from this file let relative_crates_path = diff_paths(crates_path, output_file_dir).unwrap(); // Generate Tags for the given static analysis data let tags = self.generate_tags( &source, *editioned_file_id, file_id, &this_file_dir, &relative_file_path, &refs_path, &relative_crates_path, ); // Generate html from the tags let file_path = self.vfs.file_path(file_id); let (name, extension) = file_path.name_and_extension().unwrap(); let file_name = format!("{}.{}", name, extension.unwrap_or("rs")); let out_html = html_generator::generate_html_for_tags( tags, project_info, crate_info, &source, file_name, relative_data_path.to_str().unwrap(), relative_root_dir.to_str().unwrap(), &relative_file_path, ); let n = match current_file_num.lock() { Ok(mut n) => { let c = *n; *n += 1; c } Err(_) => 0, }; println!("[{n}/{total_files}] Writing {output_file_path}.html"); std::fs::write(format!("{output_file_path}.html"), out_html.into_bytes()) .expect("Unable to write file"); }); } fn write_to_fn_search_index( func_name: &str, qualified_func_name: &str, ref_file_name: &str, fn_search_path: &AbsPathBuf, sender: &mpsc::Sender, ) { // Write out the fnSearch, this is how we search and jump to symbols if func_name.len() < 4 || func_name == "main" { return; } // the filename starts with non qualified func name's first 2 letters let file_name = &func_name[0..2].to_lowercase(); if !file_name.is_ascii() { return; } let msg = Message::WriteToFile( fn_search_path.join(file_name).to_string(), format!("{ref_file_name}|{qualified_func_name}"), ); if let Err(err) = sender.send(msg) { print_err(&format!("Failed to write to fnSearch {err}")); } } }