use crate::Format;
use crate::cached_lines::CachedLines;
use crate::opts::SourcesFrom;
use crate::{color, esafeprintln, safeprintln};
use owo_colors::OwoColorize;
use std::borrow::Cow;
use std::collections::BTreeMap;
use std::env::{current_dir, home_dir};
use std::path::{Path, PathBuf};
pub(crate) type SourceFile = (String, Option<(Source, CachedLines)>);
pub struct SourceFileIndex<'a> {
workspace: &'a Path,
sysroot: &'a Path,
path_formatter: PathFormatter,
index: BTreeMap<u64, SourceFile>,
}
impl<'a> SourceFileIndex<'a> {
pub fn new(workspace: &'a Path, sysroot: &'a Path) -> Self {
Self {
workspace,
sysroot,
path_formatter: PathFormatter {
home_dir: home_dir(),
current_dir: current_dir().unwrap_or_default(),
},
index: Default::default(),
}
}
pub fn empty() -> Self {
Self::new(Path::new(""), Path::new(""))
}
pub fn get(&self, at: u64) -> Option<&SourceFile> {
self.index.get(&at)
}
pub fn load(&mut self, f: &File<'_>, fmt: &Format) {
self.index.entry(f.index).or_insert_with(|| {
let path = f
.path
.as_full_path_with_home_dir(self.path_formatter.home_dir.as_deref());
let pretty_path = self.path_formatter.format_path(&path).display().to_string();
if fmt.verbosity > 2 {
safeprintln!("Reading file #{} {}", f.index, path.display());
}
if let Some((source, filepath)) = locate_sources(self.sysroot, self.workspace, &path) {
if fmt.verbosity > 3 {
safeprintln!("Resolved name is {filepath:?}");
}
let sources = std::fs::read_to_string(&filepath).expect("Can't read a file");
if sources.is_empty() {
if fmt.verbosity > 0 {
safeprintln!("Ignoring empty file {filepath:?}!");
}
(pretty_path, None)
} else {
if fmt.verbosity > 3 {
safeprintln!("Got {} bytes", sources.len());
}
let lines = CachedLines::without_ending(sources);
(pretty_path, Some((source, lines)))
}
} else {
if fmt.verbosity > 1 {
safeprintln!("File not found {}", path.display());
}
(pretty_path, None)
}
});
}
}
struct PathFormatter {
home_dir: Option<PathBuf>,
current_dir: PathBuf,
}
impl PathFormatter {
fn format_path<'p>(&self, path: &'p Path) -> Cow<'p, Path> {
let home = if std::path::MAIN_SEPARATOR == '/' {
"~"
} else {
"%userprofile%"
};
if path.is_absolute() {
if let Ok(rel) = path.strip_prefix(&self.current_dir) {
return rel.into();
}
if let Some(path_in_home) = self
.home_dir
.as_ref()
.and_then(|home| path.strip_prefix(home).ok())
{
return Path::new(home).join(path_in_home).into();
}
}
path.into()
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct File<'a> {
pub index: u64,
pub path: FilePath,
pub md5: Option<&'a str>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum FilePath {
FullPath(String),
PathAndFileName { path: String, name: String },
}
impl FilePath {
pub fn as_full_path(&self) -> Cow<'_, Path> {
match self {
FilePath::FullPath(path) => Cow::Borrowed(Path::new(path)),
FilePath::PathAndFileName { path, name } => Cow::Owned(Path::new(path).join(name)),
}
}
pub fn as_full_path_with_home_dir(&self, home_dir: Option<&Path>) -> Cow<'_, Path> {
let path = self.as_full_path();
if let Some(home_dir) = home_dir
&& let Ok(path_in_home) = path.strip_prefix("~")
{
return Cow::Owned(home_dir.join(path_in_home));
}
path
}
}
#[derive(Debug, Clone)]
pub enum Source {
Crate,
External,
Stdlib,
Rustc,
}
impl Source {
pub(crate) fn show_for(&self, from: SourcesFrom) -> bool {
match self {
Self::Crate => true,
Self::External => match from {
SourcesFrom::ThisWorkspace => false,
SourcesFrom::AllCrates | SourcesFrom::AllSources => true,
},
Self::Rustc | Self::Stdlib => match from {
SourcesFrom::ThisWorkspace | SourcesFrom::AllCrates => false,
SourcesFrom::AllSources => true,
},
}
}
}
pub(crate) fn print_source_location(
fname: &str,
line: u64,
content: Option<&(Source, CachedLines)>,
verbosity: usize,
) {
if content.is_none() && verbosity > 1 {
safeprintln!(
"\t\t{} {}",
color!("//", OwoColorize::cyan),
color!(
"Can't locate the file, please open a ticket with cargo-show-asm",
OwoColorize::red
),
);
}
let pos = format!("\t\t// {fname}:{line}");
safeprintln!("{}", color!(pos, OwoColorize::cyan));
if let Some((_, cached)) = content {
if let Some(src_line) = cached.get(line as usize - 1) {
safeprintln!(
"\t\t{}",
color!(src_line.trim_start(), OwoColorize::bright_red)
);
} else {
safeprintln!(
"\t\t{}",
color!(
"Corrupted rust-src installation? Try re-adding rust-src component.",
OwoColorize::red
)
);
}
}
}
pub(crate) fn locate_sources(
sysroot: &Path,
workspace: &Path,
path: &Path,
) -> Option<(Source, PathBuf)> {
let mut path = Cow::Borrowed(path);
if path.exists() {
let source = if path.starts_with(workspace) {
Source::Crate
} else {
Source::External
};
return Some((source, path.into()));
}
let no_rust_src = || {
esafeprintln!(
"You need to install rustc sources to be able to see the rust annotations, try\n\
\trustup component add rust-src"
);
std::process::exit(1);
};
if (path.starts_with("/rustc/") || path.starts_with("/private/tmp"))
&& path
.as_os_str()
.to_str()
.is_some_and(|s| s.contains('\\') && s.contains('/'))
{
let cursed_path = path
.as_os_str()
.to_str()
.expect("They are coming from a text file");
path = Cow::Owned(PathBuf::from(cursed_path.replace('\\', "/")));
}
if path.starts_with("/rustc") && path.iter().any(|c| c == "compiler") {
let mut source = sysroot.join("lib/rustlib/rustc-src/rust");
for part in path.components().skip(3) {
source.push(part);
}
if source.exists() {
return Some((Source::Rustc, source));
}
no_rust_src();
}
if path.starts_with("/rustc/") {
let mut source = sysroot.join("lib/rustlib/src/rust");
for part in path.components().skip(3) {
source.push(part);
}
if source.exists() {
return Some((Source::Stdlib, source));
}
no_rust_src();
}
if path.starts_with("/private/tmp") && path.components().any(|c| c.as_os_str() == "library") {
let mut source = sysroot.join("lib/rustlib/src/rust");
for part in path.components().skip(5) {
source.push(part);
}
if source.exists() {
return Some((Source::Stdlib, source));
}
no_rust_src();
}
if let Some(ix) = path
.components()
.position(|c| c.as_os_str() == "cargo" || c.as_os_str() == ".cargo")
.and_then(|ix| path.components().nth(ix).zip(Some(ix)))
.and_then(|(c, ix)| (c.as_os_str() == "registry").then_some(ix))
{
#[allow(deprecated)]
let mut source = home_dir().expect("No home dir?");
source.push(".cargo");
for part in path.components().skip(ix) {
source.push(part);
}
if source.exists() {
return Some((Source::External, source));
}
panic!("{path:?} looks like it can be a cargo registry reference but we failed to get it");
}
None
}