Add new search filters for types in resym

These allows searching for classes/structs, unions or enums explicitly
This commit is contained in:
ergrelet
2025-02-08 22:17:52 +01:00
parent 9164fa20c2
commit 9cd5e4e9bb
4 changed files with 155 additions and 57 deletions
+45 -25
View File
@@ -2,7 +2,7 @@ use anyhow::Result;
use eframe::egui;
use memory_logger::blocking::MemoryLogger;
use resym_core::{
backend::{Backend, BackendCommand, PDBSlot, SymbolFilters},
backend::{Backend, BackendCommand, PDBSlot, SymbolFilters, TypeFilters},
frontend::FrontendCommand,
pdb_file::{SymbolIndex, TypeIndex},
};
@@ -64,6 +64,7 @@ pub struct ResymApp {
// Components used in the left-side panel
left_panel_selected_tab: LeftPanelTab,
type_search: TextSearchComponent,
type_filters: SearchFiltersComponent<TypeFilters>,
type_list: IndexListComponent<TypeIndex>,
selected_type_index: Option<TypeIndex>,
symbol_search: TextSearchComponent,
@@ -165,6 +166,7 @@ impl ResymApp {
current_mode: ResymAppMode::Idle,
left_panel_selected_tab: LeftPanelTab::TypeSearch,
type_search: TextSearchComponent::new(),
type_filters: SearchFiltersComponent::new("Search filters"),
type_list: IndexListComponent::new(IndexListOrdering::Alphabetical),
selected_type_index: None,
symbol_search: TextSearchComponent::new(),
@@ -234,37 +236,53 @@ impl ResymApp {
match self.left_panel_selected_tab {
LeftPanelTab::TypeSearch => {
let update_type_list =
|search_query: &str, search_filters: &TypeFilters| {
// Update filtered list if filter has changed
let result = if let ResymAppMode::Comparing(..) = self.current_mode
{
self.backend.send_command(BackendCommand::ListTypesMerged(
vec![
ResymPDBSlots::Main as usize,
ResymPDBSlots::Diff as usize,
],
search_query.to_string(),
self.settings.app_settings.search_case_insensitive,
self.settings.app_settings.search_use_regex,
self.settings.app_settings.ignore_std_types,
search_filters.clone(),
))
} else {
self.backend.send_command(BackendCommand::ListTypes(
ResymPDBSlots::Main as usize,
search_query.to_string(),
self.settings.app_settings.search_case_insensitive,
self.settings.app_settings.search_use_regex,
self.settings.app_settings.ignore_std_types,
search_filters.clone(),
))
};
if let Err(err) = result {
log::error!("Failed to update type filter value: {}", err);
}
};
// Callback run when the search query changes
let on_query_update = |search_query: &str| {
// Update filtered list if filter has changed
let result = if let ResymAppMode::Comparing(..) = self.current_mode {
self.backend.send_command(BackendCommand::ListTypesMerged(
vec![
ResymPDBSlots::Main as usize,
ResymPDBSlots::Diff as usize,
],
search_query.to_string(),
self.settings.app_settings.search_case_insensitive,
self.settings.app_settings.search_use_regex,
self.settings.app_settings.ignore_std_types,
))
} else {
self.backend.send_command(BackendCommand::ListTypes(
ResymPDBSlots::Main as usize,
search_query.to_string(),
self.settings.app_settings.search_case_insensitive,
self.settings.app_settings.search_use_regex,
self.settings.app_settings.ignore_std_types,
))
};
if let Err(err) = result {
log::error!("Failed to update type filter value: {}", err);
}
let search_filters = self.type_filters.filters();
update_type_list(search_query, search_filters);
};
// Update the type search bar
ui.label("Search");
self.type_search.update(ui, &on_query_update);
// Callback run when the search filter is updated
let on_filter_update = |search_filters: &TypeFilters| {
let search_query = self.type_search.search_filter();
update_type_list(search_query, search_filters);
};
self.type_filters.update(ui, &on_filter_update);
ui.separator();
ui.add_space(4.0);
@@ -681,6 +699,7 @@ impl ResymApp {
false,
false,
self.settings.app_settings.ignore_std_types,
Default::default(),
)) {
log::error!("Failed to update type filter value: {}", err);
}
@@ -734,6 +753,7 @@ impl ResymApp {
false,
false,
self.settings.app_settings.ignore_std_types,
Default::default(),
))
{
log::error!("Failed to update type filter value: {}", err);
+82 -27
View File
@@ -29,7 +29,7 @@ use crate::{
par_iter_if_available, par_sort_by_if_available,
pdb_file::{
self, ModuleInfo, ModuleList, PDBDataSource, PdbFile, SymbolInfoEx, SymbolKind, SymbolList,
SymbolListExView, TypeList,
SymbolListExView, TypeInfoEx, TypeKind, TypeList, TypeListExView,
},
pdb_types::{include_headers_for_flavor, PrimitiveReconstructionFlavor},
PKG_VERSION,
@@ -82,10 +82,10 @@ pub enum BackendCommand {
bool,
),
/// Retrieve a list of types that match the given filter for a given PDB.
ListTypes(PDBSlot, String, bool, bool, bool),
ListTypes(PDBSlot, String, bool, bool, bool, TypeFilters),
/// Retrieve a list of types that match the given filter for multiple PDBs
/// and merge the result.
ListTypesMerged(Vec<PDBSlot>, String, bool, bool, bool),
ListTypesMerged(Vec<PDBSlot>, String, bool, bool, bool, TypeFilters),
/// Retrieve a list of symbols that match the given filter for multiple PDBs
/// and merge the result.
ListSymbols(PDBSlot, String, bool, bool, bool, SymbolFilters),
@@ -143,6 +143,24 @@ pub enum BackendCommand {
ListTypeCrossReferences(PDBSlot, pdb_file::TypeIndex),
}
/// Search filters for types
#[derive(Clone, Reflect)]
pub struct TypeFilters {
classes: bool,
unions: bool,
enums: bool,
}
impl Default for TypeFilters {
fn default() -> Self {
Self {
classes: true,
unions: true,
enums: true,
}
}
}
/// Search filters for symbols
#[derive(Clone, Reflect)]
pub struct SymbolFilters {
@@ -416,19 +434,21 @@ fn worker_thread_routine(
BackendCommand::ListTypes(
pdb_slot,
search_filter,
search_query,
case_insensitive_search,
use_regex,
ignore_std_types,
search_filters,
) => {
if let Some(pdb_file) = pdb_files.get(&pdb_slot) {
let filtered_type_list = update_type_filter_command(
pdb_file,
&search_filter,
&search_query,
case_insensitive_search,
use_regex,
ignore_std_types,
true,
search_filters,
);
frontend_controller
.send_command(FrontendCommand::ListTypesResult(filtered_type_list))?;
@@ -437,21 +457,23 @@ fn worker_thread_routine(
BackendCommand::ListTypesMerged(
pdb_slots,
search_filter,
search_query,
case_insensitive_search,
use_regex,
ignore_std_types,
search_filters,
) => {
let mut filtered_type_set = BTreeSet::default();
for pdb_slot in pdb_slots {
if let Some(pdb_file) = pdb_files.get(&pdb_slot) {
let filtered_type_list = update_type_filter_command(
pdb_file,
&search_filter,
&search_query,
case_insensitive_search,
use_regex,
ignore_std_types,
false,
search_filters.clone(),
);
filtered_type_set.extend(filtered_type_list.into_iter().map(|(s, _)| {
// Collapse all type indices to `default`. When merging
@@ -917,33 +939,41 @@ where
fn update_type_filter_command<T>(
pdb_file: &PdbFile<T>,
search_filter: &str,
search_query: &str,
case_insensitive_search: bool,
use_regex: bool,
ignore_std_types: bool,
sort_by_index: bool,
search_filters: TypeFilters,
) -> TypeList
where
T: io::Seek + io::Read,
T: io::Seek + io::Read + std::fmt::Debug,
{
let filter_start = Instant::now();
// Filter out std types if needed
let filtered_type_list = if ignore_std_types {
filter_std_types(&pdb_file.complete_type_list)
} else {
pdb_file.complete_type_list.clone()
};
// Retrieve view to the type list
let type_list: TypeListExView = pdb_file.type_list();
// Filter types following the search filter
let mut filtered_type_list = if search_filter.is_empty() {
let filtered_type_list = if search_query.is_empty() {
// No need to filter
filtered_type_list
type_list
} else if use_regex {
filter_types_regex(&filtered_type_list, search_filter, case_insensitive_search)
filter_types_regex(&type_list, search_query, case_insensitive_search)
} else {
filter_types_regular(&filtered_type_list, search_filter, case_insensitive_search)
filter_types_regular(&type_list, search_query, case_insensitive_search)
};
// Apply filters by type kind
let filtered_type_list = filter_types_kind(&filtered_type_list, &search_filters);
// Filter out std types if needed
let mut filtered_type_list = if ignore_std_types {
filter_std_types(&filtered_type_list)
} else {
filtered_type_list
};
if sort_by_index {
// Order types by type index, so the order is deterministic
// (i.e., independent from DashMap's hash function)
@@ -955,15 +985,18 @@ where
filter_start.elapsed().as_millis()
);
filtered_type_list
// Convert refs to symbol info into clones and return it
par_iter_if_available!(filtered_type_list)
.map(|type_info| (type_info.0.clone(), type_info.1))
.collect()
}
/// Filter type list with a regular expression
fn filter_types_regex(
type_list: &[(String, u32)],
fn filter_types_regex<'s>(
type_list: &'s [&TypeInfoEx],
search_filter: &str,
case_insensitive_search: bool,
) -> TypeList {
) -> TypeListExView<'s> {
match regex::RegexBuilder::new(search_filter)
.case_insensitive(case_insensitive_search)
.build()
@@ -978,11 +1011,11 @@ fn filter_types_regex(
}
/// Filter type list with a plain (sub-)string
fn filter_types_regular(
type_list: &[(String, u32)],
fn filter_types_regular<'s>(
type_list: &'s [&TypeInfoEx],
search_filter: &str,
case_insensitive_search: bool,
) -> TypeList {
) -> TypeListExView<'s> {
if case_insensitive_search {
let search_filter = search_filter.to_lowercase();
par_iter_if_available!(type_list)
@@ -998,13 +1031,35 @@ fn filter_types_regular(
}
/// Filter type list to remove types in the `std` namespace
fn filter_std_types(type_list: &[(String, pdb_file::TypeIndex)]) -> TypeList {
fn filter_std_types<'s>(type_list: &'s [&TypeInfoEx]) -> TypeListExView<'s> {
par_iter_if_available!(type_list)
.filter(|r| !r.0.starts_with("std::"))
.cloned()
.collect()
}
/// Filter type list with a regular expression
#[allow(clippy::if_same_then_else, clippy::needless_bool)]
fn filter_types_kind<'s>(
type_list: &'s [&TypeInfoEx],
type_filters: &TypeFilters,
) -> TypeListExView<'s> {
par_iter_if_available!(type_list)
.filter(|(_, _, type_kind)| {
if !type_filters.classes && *type_kind == TypeKind::Class {
false
} else if !type_filters.unions && *type_kind == TypeKind::Union {
false
} else if !type_filters.enums && *type_kind == TypeKind::Enum {
false
} else {
true
}
})
.cloned()
.collect()
}
fn update_symbol_filter_command<T>(
pdb_file: &mut PdbFile<T>,
search_query: &str,
+26 -5
View File
@@ -26,8 +26,21 @@ use crate::{
// Type
pub type TypeIndex = u32;
/// Minimal symbol info (used by frontends)
pub type TypeInfo = (String, TypeIndex);
pub type TypeList = Vec<TypeInfo>;
/// Extended symbol info (used by the backend)
pub type TypeInfoEx = (String, TypeIndex, TypeKind);
pub type TypeListEx = Vec<TypeInfoEx>;
pub type TypeListExView<'t> = Vec<&'t TypeInfoEx>;
#[derive(Eq, PartialEq)]
pub enum TypeKind {
Class,
Union,
Enum,
Unknown,
}
// Symbol
/// `SymbolIndex` have two parts: a module index and a symbol index
pub type SymbolIndex = (ModuleIndex, u32);
@@ -45,6 +58,7 @@ pub enum SymbolKind {
Type,
Unknown,
}
// Module
pub type ModuleIndex = usize;
pub type ModuleInfo = (String, ModuleIndex);
@@ -105,7 +119,7 @@ pub struct PdbFile<'p, T>
where
T: io::Seek + io::Read + 'p,
{
pub complete_type_list: Vec<(String, TypeIndex)>,
pub complete_type_list: TypeListEx,
pub forwarder_to_complete_type: Arc<DashMap<pdb::TypeIndex, pdb::TypeIndex>>,
pub symbol_list: SymbolListEx,
pub machine_type: pdb::MachineType,
@@ -246,7 +260,8 @@ where
if is_unnamed_type(&class_name) {
class_name = format!("_unnamed_{type_index}");
}
self.complete_type_list.push((class_name, type_index.0));
self.complete_type_list
.push((class_name, type_index.0, TypeKind::Class));
}
pdb::TypeData::Union(data) => {
let mut class_name = data.name.to_string().into_owned();
@@ -262,7 +277,8 @@ where
if is_unnamed_type(&class_name) {
class_name = format!("_unnamed_{type_index}");
}
self.complete_type_list.push((class_name, type_index.0));
self.complete_type_list
.push((class_name, type_index.0, TypeKind::Union));
}
pdb::TypeData::Enumeration(data) => {
let mut class_name = data.name.to_string().into_owned();
@@ -278,7 +294,8 @@ where
if is_unnamed_type(&class_name) {
class_name = format!("_unnamed_{type_index}");
}
self.complete_type_list.push((class_name, type_index.0));
self.complete_type_list
.push((class_name, type_index.0, TypeKind::Enum));
}
_ => {}
}
@@ -435,6 +452,10 @@ where
)
}
pub fn type_list(&self) -> TypeListExView {
self.complete_type_list.iter().collect()
}
pub fn symbol_list(&mut self) -> Result<SymbolListExView> {
// If cache is populated, return the cached list
if !self.symbol_list.is_empty() {
@@ -1057,7 +1078,7 @@ where
fn type_list_from_type_indices(&self, type_indices: &[TypeIndex]) -> TypeList {
par_iter_if_available!(self.complete_type_list)
.filter_map(|(type_name, type_index)| {
.filter_map(|(type_name, type_index, _)| {
if type_indices.contains(type_index) {
Some((type_name.clone(), *type_index))
} else {
+2
View File
@@ -64,6 +64,8 @@ impl ResymcApp {
case_insensitive,
use_regex,
ignore_std_types,
// TODO(ergrelet): allow users to control these filters
Default::default(),
))?;
// Wait for the backend to finish filtering types
if let FrontendCommand::ListTypesResult(type_list) =