//! Virtual documents: read-only contents a language server provides for a //! URI (library classes from `jdt://`, disk classes from `file://`, or //! `workspace/textDocumentContent`). use percent_encoding::percent_decode_str; /// Recognize decompiler headers, without treating disassembler stubs as Java source. pub fn is_decompiled(text: &str) -> bool { let head: String = text.chars().take(600).collect(); head.lines() .take(12) .take_while(|line| { let line = line.trim(); line.is_empty() || line.starts_with("//") || line.starts_with("/*") || line.starts_with('*') }) .any(|line| { let line = line.trim().trim_start_matches('/').trim_start_matches('*').trim().to_ascii_lowercase(); [ "source code is unavailable, and was generated by the fernflower decompiler.", "source code is decompiled from a .class file using fernflower decompiler", "decompiled with cfr", "decompiled with procyon", ] .iter() .any(|marker| line.starts_with(marker)) }) } /// `scheme` of a URI (`jdt` for `jdt://…`). pub fn scheme(uri: &str) -> &str { uri.split_once(':').map(|(s, _)| s).unwrap_or("") } /// The URI's path segments (without query / fragment), percent-decoded. fn segments(uri: &str) -> Vec { let rest = uri.split_once(':').map(|(_, r)| r).unwrap_or(uri); let rest = rest.split(['?', '#']).next().unwrap_or(rest); let rest = rest.strip_prefix("//").unwrap_or(rest); rest.split('/').filter(|s| !s.is_empty()).map(|s| percent_decode_str(s).decode_utf8_lossy().into_owned()).collect() } /// Editor tab title: the last path segment; a class file whose real source /// was found is shown as `.java` (IDEA: `String.java` vs `String.class`). pub fn title(uri: &str, decompiled: bool) -> String { let mut name = segments(uri).pop().unwrap_or_else(|| uri.to_owned()); // Recent jdtls versions use a .java path even for decompiled class handles. if decompiled && scheme(uri) == "jdt" && name.ends_with(".java") && uri.split_once('?').is_some_and(|(_, q)| percent_decode_str(q).decode_utf8_lossy().contains(".class")) { name.truncate(name.len() - 5); name.push_str(".class"); } match name.strip_suffix(".class") { Some(stem) if !decompiled && scheme(uri) == "jdt" => format!("{stem}.java"), _ => name, } } /// Where the document comes from, e.g. the jar path of a `jdt://` class. pub fn description(uri: &str) -> Option { if scheme(uri) == "jar" { return crate::library_source::parse(uri).map(|e| e.jar.to_string_lossy().into_owned()); } if scheme(uri) != "jdt" { return None; } let segs = segments(uri); // jdt://contents///?=/