mirror of
https://github.com/ergrelet/resym
synced 2026-06-08 14:04:27 +00:00
Add new search filters for types in resym
These allows searching for classes/structs, unions or enums explicitly
This commit is contained in:
+45
-25
@@ -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
@@ -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,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 {
|
||||
|
||||
@@ -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) =
|
||||
|
||||
Reference in New Issue
Block a user